Merge branch 'master' into reset-loading-on-manual-cancel

This commit is contained in:
Victor Lopez 2021-03-06 22:39:36 -06:00
commit 8688277d5e
5 changed files with 131 additions and 53 deletions

View File

@ -2,6 +2,25 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [2.5.0-0](https://github.com/simoneb/axios-hooks/compare/v2.4.1...v2.5.0-0) (2021-03-06)
### Features
* no config serialization ([15fe158](https://github.com/simoneb/axios-hooks/commit/15fe1588448fc58b0b5b83815cc3a12812a466a2))
### Bug Fixes
* tests ([4176d94](https://github.com/simoneb/axios-hooks/commit/4176d9489085febda797dabddb104141197a901c))
### [2.4.1](https://github.com/simoneb/axios-hooks/compare/v2.4.0...v2.4.1) (2021-03-06)
### Bug Fixes
* regression in config serialization ([c5e8645](https://github.com/simoneb/axios-hooks/commit/c5e86450811b64e1babdd2cff97ad463b5ee83b2))
## [2.4.0](https://github.com/simoneb/axios-hooks/compare/v2.4.0-0...v2.4.0) (2021-03-05)

7
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "axios-hooks",
"version": "2.4.0",
"version": "2.5.0-0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -6704,6 +6704,11 @@
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"dequal": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz",
"integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug=="
},
"detect-indent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "axios-hooks",
"version": "2.4.0",
"version": "2.5.0-0",
"description": "axios-hooks",
"keywords": [
"axios",
@ -34,6 +34,7 @@
},
"dependencies": {
"@babel/runtime": "7.13.9",
"dequal": "2.0.2",
"lru-cache": "6.0.0"
},
"peerDependencies": {

View File

@ -1,6 +1,7 @@
import React from 'react'
import StaticAxios from 'axios'
import LRU from 'lru-cache'
import { dequal as deepEqual } from 'dequal/lite'
const actions = {
REQUEST_START: 'REQUEST_START',
@ -53,7 +54,7 @@ function configToObject(config) {
}
}
return config
return Object.assign({}, config)
}
export function makeUseAxios(configureOptions) {
@ -217,13 +218,17 @@ export function makeUseAxios(configureOptions) {
)
}
function useAxios(_config, options) {
const config = React.useMemo(() => configToObject(_config), [_config])
options = React.useMemo(
() => ({ ...defaultOptions, ...options }),
function useAxios(_config, _options) {
const config = React.useMemo(
() => configToObject(_config),
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(options)]
useDeepCompareMemoize(_config)
)
const options = React.useMemo(
() => ({ ...defaultOptions, ..._options }),
// eslint-disable-next-line react-hooks/exhaustive-deps
useDeepCompareMemoize(_options)
)
const isCancelledManually = React.useRef(false)
@ -307,3 +312,15 @@ export function makeUseAxios(configureOptions) {
return [state, refetch, cancelManually]
}
}
function useDeepCompareMemoize(value) {
const ref = React.useRef()
const signalRef = React.useRef(0)
if (!deepEqual(value, ref.current)) {
ref.current = value
signalRef.current += 1
}
return [signalRef.current]
}

View File

@ -66,7 +66,7 @@ describe('makeUseAxios', () => {
const setup = makeSetup(makeUseAxios({ axios: mockAxios }))
const { waitForNextUpdate } = setup()
const { waitForNextUpdate } = setup('')
expect(mockAxios).toHaveBeenCalled()
@ -83,7 +83,7 @@ describe('makeUseAxios', () => {
it('should use local state across rerenders', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { waitForNextUpdate, rerender } = setup()
const { waitForNextUpdate, rerender } = setup('')
await waitForNextUpdate()
@ -95,13 +95,13 @@ describe('makeUseAxios', () => {
it('should hit network across component mounts', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, unmount } = setup()
const { waitForNextUpdate, unmount } = setup('')
await waitForNextUpdate()
unmount()
await setup().waitForNextUpdate()
await setup('').waitForNextUpdate()
expect(axios).toHaveBeenCalledTimes(2)
})
@ -114,7 +114,7 @@ describe('makeUseAxios', () => {
makeUseAxios({ defaultOptions: { manual: true } })
)
setup()
setup('')
expect(axios).not.toHaveBeenCalled()
})
@ -128,13 +128,13 @@ describe('makeUseAxios', () => {
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, unmount } = setup()
const { waitForNextUpdate, unmount } = setup('')
await waitForNextUpdate()
unmount()
await setup().waitForNextUpdate()
await setup('').waitForNextUpdate()
expect(axios).toHaveBeenCalledTimes(2)
})
@ -175,7 +175,7 @@ describe('makeUseAxios', () => {
})
function makeSetup(useAxios) {
return (config = '', options = null) =>
return (config, options = undefined) =>
renderHook(
({ config, options }) => {
return useAxios(config, options)
@ -202,7 +202,7 @@ function standardTests(
it('should set loading to true and error to null before the request resolves', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { result, waitForNextUpdate } = setup()
const { result, waitForNextUpdate } = setup('')
expect(result.current[0].loading).toBe(true)
expect(result.current[0].error).toBe(null)
@ -213,7 +213,7 @@ function standardTests(
it('should set loading to false when request resolves', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { result, waitForNextUpdate } = setup()
const { result, waitForNextUpdate } = setup('')
await waitForNextUpdate()
@ -227,7 +227,7 @@ function standardTests(
axios.mockResolvedValueOnce(response)
const { result, waitForNextUpdate } = setup()
const { result, waitForNextUpdate } = setup('')
await waitForNextUpdate()
@ -241,7 +241,7 @@ function standardTests(
axios.mockRejectedValueOnce(error)
const { result, waitForNextUpdate } = setup()
const { result, waitForNextUpdate } = setup('')
await waitForNextUpdate()
@ -253,7 +253,7 @@ function standardTests(
axios.mockRejectedValueOnce(error)
const { result, waitForNextUpdate, rerender } = setup()
const { result, waitForNextUpdate, rerender } = setup('')
await waitForNextUpdate()
@ -271,7 +271,7 @@ function standardTests(
axios.mockRejectedValueOnce(error)
const firstRender = setup()
const firstRender = setup('')
await firstRender.waitForNextUpdate()
@ -279,7 +279,7 @@ function standardTests(
axios.mockResolvedValueOnce({ data: 'whatever' })
const secondRender = setup()
const secondRender = setup('')
await secondRender.waitForNextUpdate()
@ -291,7 +291,7 @@ function standardTests(
axios.mockRejectedValueOnce(error)
const { result, waitForNextUpdate } = setup()
const { result, waitForNextUpdate } = setup('')
await waitForNextUpdate()
@ -314,7 +314,7 @@ function standardTests(
axios.mockRejectedValueOnce(error)
const { result, waitForNextUpdate } = setup()
const { result, waitForNextUpdate } = setup('')
await waitForNextUpdate()
@ -325,7 +325,7 @@ function standardTests(
it('should refetch', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { result, waitForNextUpdate } = setup()
const { result, waitForNextUpdate } = setup('')
await waitForNextUpdate()
@ -342,7 +342,7 @@ function standardTests(
it('should return the same reference to the fetch function', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { result, rerender, waitForNextUpdate } = setup()
const { result, rerender, waitForNextUpdate } = setup('')
const firstRefetch = result.current[1]
@ -358,9 +358,9 @@ function standardTests(
axios.mockResolvedValueOnce(response)
await setup().waitForNextUpdate()
await setup('').waitForNextUpdate()
const { result } = setup()
const { result } = setup('')
expect(result.current[0]).toEqual({
loading: false,
@ -376,7 +376,7 @@ function standardTests(
it('should provide the cancel token to axios', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { waitForNextUpdate } = setup()
const { waitForNextUpdate } = setup('')
expect(axios).toHaveBeenCalledWith(
expect.objectContaining({
@ -390,7 +390,7 @@ function standardTests(
it('should cancel the outstanding request when the component unmounts', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { waitForNextUpdate, unmount } = setup()
const { waitForNextUpdate, unmount } = setup('')
await waitForNextUpdate()
@ -402,7 +402,7 @@ function standardTests(
it('should cancel the outstanding request when the cancel method is called', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, result } = setup()
const { waitForNextUpdate, result } = setup('')
await waitForNextUpdate()
@ -426,7 +426,7 @@ function standardTests(
await waitForNextUpdate()
})
it('should not cancel the outstanding request when the component rerenders with same config', async () => {
it('should not cancel the outstanding request when the component rerenders with same string config', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, rerender } = setup('initial config')
@ -438,6 +438,42 @@ function standardTests(
expect(cancel).not.toHaveBeenCalled()
})
it('should not cancel the outstanding request when the component rerenders with same object config', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, rerender } = setup({ some: 'config' })
await waitForNextUpdate()
rerender()
expect(cancel).not.toHaveBeenCalled()
})
it('should not cancel the outstanding request when the component rerenders with equal string config', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, rerender } = setup('initial config', {})
await waitForNextUpdate()
rerender({ config: 'initial config', options: {} })
expect(cancel).not.toHaveBeenCalled()
})
it('should not cancel the outstanding request when the component rerenders with equal object config', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, rerender } = setup({ some: 'config' }, {})
await waitForNextUpdate()
rerender({ config: { some: 'config' }, options: {} })
expect(cancel).not.toHaveBeenCalled()
})
it('should cancel the outstanding request when the cancel method is called after the component rerenders with same config', async () => {
axios.mockResolvedValue({ data: 'whatever' })
@ -461,7 +497,7 @@ function standardTests(
.fn()
.mockImplementationOnce(err => err === cancellation)
const { result, waitFor } = setup()
const { result, waitFor } = setup('')
// if we cancel we won't dispatch the error, hence there's no state update
// to wait for. yet, if we don't try to wait, we won't know if we're handling
@ -553,7 +589,7 @@ function standardTests(
it('should cancel the outstanding manual refetch when the component refetches', async () => {
axios.mockResolvedValue({ data: 'whatever' })
const { result, waitForNextUpdate, rerender } = setup()
const { result, waitForNextUpdate, rerender } = setup('')
act(() => {
result.current[1]()
@ -624,7 +660,7 @@ function standardTests(
axios.mockResolvedValueOnce(response)
// first component renders and stores results in cache
await setup().waitForNextUpdate()
await setup('').waitForNextUpdate()
const { result } = setup('', { manual: true })
@ -658,7 +694,7 @@ function standardTests(
current: [, refetch]
},
waitForNextUpdate
} = setup()
} = setup('')
act(() => {
expect(refetch()).resolves.toEqual(response)
@ -679,7 +715,7 @@ function standardTests(
current: [, refetch]
},
waitForNextUpdate
} = setup()
} = setup('')
await waitForNextUpdate()
@ -702,7 +738,7 @@ function standardTests(
current: [, refetch]
},
waitForNextUpdate
} = setup()
} = setup('')
await waitForNextUpdate()
@ -723,7 +759,7 @@ function standardTests(
current: [, refetch]
},
waitForNextUpdate
} = setup()
} = setup('')
await waitForNextUpdate()
@ -898,7 +934,7 @@ function standardTests(
it('should not return response even if there is a cached one', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
await setup().waitForNextUpdate()
await setup('').waitForNextUpdate()
const { result } = setup('', { manual: true })
@ -910,7 +946,7 @@ function standardTests(
it('should use local state across rerenders', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { waitForNextUpdate, rerender } = setup()
const { waitForNextUpdate, rerender } = setup('')
await waitForNextUpdate()
@ -922,13 +958,13 @@ function standardTests(
it('should not hit network across component mounts by default', async () => {
axios.mockResolvedValueOnce({ data: 'whatever' })
const { waitForNextUpdate, unmount } = setup()
const { waitForNextUpdate, unmount } = setup('')
await waitForNextUpdate()
unmount()
setup()
setup('')
expect(axios).toHaveBeenCalledTimes(1)
})
@ -968,7 +1004,7 @@ function standardTests(
unmount()
setup()
setup('')
expect(axios).toHaveBeenCalledTimes(1)
})
@ -1044,7 +1080,7 @@ function standardTests(
configure({ axios: mockAxios })
const { waitForNextUpdate } = setup()
const { waitForNextUpdate } = setup('')
expect(mockAxios).toHaveBeenCalled()
@ -1057,7 +1093,7 @@ function standardTests(
axios.mockResolvedValueOnce({ data: 'whatever' })
const { waitForNextUpdate, rerender } = setup()
const { waitForNextUpdate, rerender } = setup('')
await waitForNextUpdate()
@ -1071,13 +1107,13 @@ function standardTests(
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, unmount } = setup()
const { waitForNextUpdate, unmount } = setup('')
await waitForNextUpdate()
unmount()
await setup().waitForNextUpdate()
await setup('').waitForNextUpdate()
expect(axios).toHaveBeenCalledTimes(2)
})
@ -1088,7 +1124,7 @@ function standardTests(
it('should override default manual option', () => {
configure({ defaultOptions: { manual: true } })
setup()
setup('')
expect(axios).not.toHaveBeenCalled()
})
@ -1100,13 +1136,13 @@ function standardTests(
axios.mockResolvedValue({ data: 'whatever' })
const { waitForNextUpdate, unmount } = setup()
const { waitForNextUpdate, unmount } = setup('')
await waitForNextUpdate()
unmount()
await setup().waitForNextUpdate()
await setup('').waitForNextUpdate()
expect(axios).toHaveBeenCalledTimes(2)
})