Created
August 26, 2022 12:04
-
-
Save cosemansp/7715f8ddae304566ec3403bba9a95d70 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import useApi from './useApi'; | |
import { useMount } from './useMount'; | |
type MutationOptions<TData, TError> = { | |
onError?: (err: TError, config: MutationConfig) => void; | |
onSuccess?: (data: TData, config: MutationConfig) => void; | |
}; | |
type MutationConfig = { | |
data?: unknown; | |
params?: Record<string, number | string | boolean | Date>; | |
}; | |
/** | |
* Usage | |
* | |
* // simple mutation | |
* const api = useApi(); | |
* api.post('/api/user', { name: 'john' }); | |
* | |
* // handling mutation state | |
* const { mutate as addUser, data, error, isLoading } = useMutation('PUT', '/api/users'); | |
* | |
* addUser({ | |
* data: { name: 'john' }, | |
* params: { id: 123 } | |
* }); | |
* | |
* // responding to mutation state | |
* // and result action | |
* const { mutate, error, isLoading } = useMutation('POST', '/api/users', { | |
* onSuccess(data) { | |
* # refresh useQuery | |
* refetch(); | |
* } | |
* }); | |
* mutate({ | |
* data: { name: 'john' } | |
* }); | |
* | |
*/ | |
const useMutation = <TData, TError = Error>( | |
method: 'POST' | 'PUT' | 'PATCH' | 'DELETE', | |
url: string, | |
options: MutationOptions<TData, TError> = {}, | |
) => { | |
const api = useApi(); | |
const [data, setData] = React.useState<TData | undefined>(); | |
const [error, setError] = React.useState<TError | undefined>(undefined); | |
const [isLoading, setLoading] = React.useState(false); | |
const controllerRef = React.useRef(new AbortController()); | |
const mutateFn = React.useCallback( | |
async (config: MutationConfig = {}): Promise<TData | void> => { | |
setLoading(true); | |
return api | |
.request<TData>({ | |
url, | |
method, | |
data: config.data, | |
signal: controllerRef.current.signal, // to abort the request | |
params: { | |
...(config.params ? config.params : {}), | |
}, | |
}) | |
.then((result) => { | |
setData(result.data); | |
if (options.onSuccess) { | |
options.onSuccess(result.data, config); | |
} | |
return result.data; | |
}) | |
.catch((err) => { | |
setError(err); | |
if (options.onError) { | |
options.onError(err, config); | |
} | |
}) | |
.finally(() => { | |
setLoading(false); | |
}); | |
}, | |
[url, api, options, method], | |
); | |
useMount(() => () => { | |
// abort the pending request when unmounting | |
controllerRef.current.abort(); | |
}); | |
return { data, error, isLoading, mutate: mutateFn }; | |
}; | |
export default useMutation; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { waitFor, renderHook } from '@testing-library/react'; | |
import nock from 'nock'; | |
import { act } from 'react-dom/test-utils'; | |
import useMutation from './useMutation'; | |
describe('useMutation', () => { | |
test('post data & handle response', async () => { | |
// arrange | |
const testData = { | |
id: 123, | |
name: 'john', | |
}; | |
type ResponseData = typeof testData; | |
nock('http://localhost:8080').post('/api/user').reply(200, testData); | |
// act | |
const { result } = renderHook(() => | |
useMutation<ResponseData>('POST', '/api/user', { | |
onSuccess: (data) => { | |
expect(data).toEqual(testData); | |
}, | |
}), | |
); | |
// call mutate to trigger the post/put/delete request | |
act(() => { | |
result.current.mutate({ | |
data: { | |
name: 'john', | |
}, | |
}); | |
}); | |
// assert | |
const previousResult = result.current; | |
await waitFor(() => { | |
expect(result.current).not.toBe(previousResult); | |
}); | |
expect(result.current.isLoading).toBe(false); | |
expect(result.current.data).toEqual(testData); | |
}); | |
test('post data and params', async () => { | |
// arrange | |
const testData = { | |
id: 123, | |
name: 'john', | |
}; | |
type ResponseData = typeof testData; | |
nock('http://localhost:8080') | |
.put('/api/user') | |
.query({ | |
id: 123, | |
}) | |
.reply(200, testData); | |
// act | |
const { result } = renderHook(() => useMutation<ResponseData>('PUT', '/api/user')); | |
// call mutate to trigger the post/put/delete request | |
act(() => { | |
result.current.mutate({ | |
data: { | |
name: 'john', | |
}, | |
params: { | |
id: 123, | |
}, | |
}); | |
}); | |
// assert | |
const previousResult = result.current; | |
await waitFor(() => { | |
expect(result.current).not.toBe(previousResult); | |
}); | |
expect(result.current.error).toBeUndefined(); | |
expect(result.current.isLoading).toBe(false); | |
expect(result.current.data).toEqual(testData); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment