Run functions with side-effects safely in React
npm install use-mutation
Specially useful to run requests against an API, and combined with SWR.
Install it:
``sh`
$ yarn add use-mutation
Import it:
`ts`
import useMutation from 'use-mutation';
Create a function which runs a mutation
`tsx`
async function createComment({
authorId,
comment,
}: {
authorId: number;
comment: string;
}) {
const res = await fetch('/api/comments', {
method: 'POST',
body: JSON.stringify({ authorId, comment }),
});
if (!res.ok) throw new Error(res.statusText);
return await res.json();
}
Use your function with useMutation
`tsx
function CommentForm({ authorId }) {
const [comment, setComment] = React.useState('');
const [mutate, { status }] = useMutation(createComment, {
onMutate({ input }) {
// do something before the mutation run
return () => {
// rollback changes if the mutation failed
};
},
onSuccess({ data, input }) {
// do something once the mutation succeeded
},
onFailure({ error, rollback, input }) {
// do something once the mutation failed
},
onSettled({ status, error, data, rollback, input }) {
switch (status) {
case 'success': {
// do something if the mutation succeeded
}
case 'failure': {
// do something if the mutation failed
}
}
},
});
const handleSubmit = React.useCallback(
function handleSubmit(event) {
mutate({ authorId, comment });
},
[mutate, comment]
);
// render your UI
}
`
If you are using SWR, you can use useMutation to run your mutations to perform Optimistic UI changes.
`tsx
import { cache, mutate } from 'swr';
function createComment(input) {
// ...
}
function useCreateComment() {
return useMutation(createComment, {
onMutate({ input }) {
const oldData = cache.get('comment-list');
// optimistically update the data before your mutation is run
mutate('comment-list', current => current.concat(input), false);
return () => mutate('comment-list', oldData, false); // rollback if it failed
},
onFailure({ status, rollback }) {
if (status === 'failure' && rollback) rollback();
},
});
}
`
This way when you run mutate, it will first optimistically update your SWR cache and if it fails it will rollback to the old data.
`tsx
const [mutate, { status, data, error, reset }] = useMutation<
Input,
Data,
Error
>(mutationFn, {
onMutate,
onSuccess,
onFailure,
onSettled,
throwOnFailure,
useErrorBoundary,
});
const promise = mutate(input, {
onSuccess,
onSettled,
onError,
throwOnFailure,
});
`
> Only if you are using TypeScript
- Input = anyData = any
- The data your mutation function needs to run
- Error = any
- The data the hook will return as result of your mutation
-
- The error the hook will return as a failure in your mutation
- mutationFn(input: Input): PromiseonMutate?({ input: Input }): Promise
- Required
- A function to be executed before the mutation runs.
- It receives the same input as the mutate function.
- It can be an async or sync function, in both cases if it returns a function it will keep it as a way to rollback the changed applied inside onMutate.
- onMutate
- Optional
- A function to be executed before the mutation runs.
- It receives the same input as the mutate function.
- It can be an async or sync function, in both cases if it returns a function.
- it will keep it as a way to rollback the changed applied inside onSuccess?({ data: Data, input: Input }): Promise
- onFailure?({ error: Error, rollback: rollbackFn, input: Input }): Promise
- Optional
- A function to be executed after the mutation resolves successfully.
- It receives the result of the mutation.
- If a Promise is returned, it will be awaited before proceeding.
- onSettled?({ status: 'success' | 'failure', error?: Error, data?: Data, rollback?: rollbackFn, input: Input}): Promise
- Optional
- A function to be executed after the mutation failed to execute.
- If a Promise is returned, it will be awaited before proceeding.
- throwOnFailure?: boolean
- Optional
- A function to be executed after the mutation has resolves, either successfully or as failure.
- This function receives the error or the result of the mutation.
- If a Promise is returned, it will be awaited before proceeding.
- true
- Optional
- If defined as , a failure in the mutation will cause the mutate function to throw. Disabled by default.useErrorBoundary?: boolean
- (default false)true
- Optional
- If defined as , a failure in the mutation will cause the Hook to throw in render time, making error boundaries catch the error.
- mutate(input: Input, config: Omitstatus: 'idle' | 'running' | 'success' | 'failure'
- The function you call to trigger your mutation, passing the input your mutation function needs.
- All the lifecycle callback defined here will run _after_ the callback defined in the Hook.
- idle
- The current status of the mutation, it will be:
- initial status of the hook, and the status after a resetrunning
- if the mutation is currently runningsuccess
- if the mutation resolved successfullyfailure
- if the mutation failed to resolvedata: Data
- error: Error
- The data returned as the result of the mutation.
- reset(): void
- The error returned as the result of the mutation.
-
- A function to reset the internal state of the Hook to the orignal idle and clear any data or error.
- Sergio Xalambrí - Able
The MIT License.