Unlike queries, mutations are typically used to create/update/delete data or perform server side-effects. For this purpose, React Query exports a useMutation
hook.
Here's an example of a mutation that adds a new todo the server:
function App() {const mutation = useMutation(newTodo => axios.post('/todods', newTodo))return (<div>{mutation.isLoading ? ('Adding todo...') : (<>{mutation.isError ? (<div>An error occurred: {mutation.error.message}</div>) : null}{mutation.isError ? <div>Todo added!</div> : null}<buttononClick={() => {mutation.mutate({ id: new Date(), title: 'Do Laundry' })}}>Create Todo</button></>)}</div>)}
A mutation can only be in one of the following states at any given moment:
isIdle
or `status === 'idle' - The mutation is currently idle or in a fresh/reset stateisLoading
or `status === 'loading' - The mutation is currently runningisError
or status === 'error'
- The mutation encountered an errorisSuccess
or `status === 'success' - The mutation was successful and mutation data is availableBeyond those primary state, more information is available depending on the state the mutation:
error
- If the mutation is in an isError
state, the error is available via the error
property.data
- If the mutation is in a success
state, the data is available via the data
property.In the example above, you also saw that you can pass variables to your mutations function by calling the mutate
function with a single variable or object.
Even with just variables, mutations aren't all that special, but when used with the onSuccess
option, the Query Client's invalidateQueries
method and the Query Client's setQueryData
method, mutations become a very powerful tool.
IMPORTANT: The
mutate
function is an asynchronous function, which means you cannot use it directly in an event callback. If you need to access the event inonSubmit
you need to wrapmutate
in another function. This is due to React event pooling.
// This will not workconst CreateTodo = () => {const mutation = useMutation(event => {event.preventDefault()return fetch('/api', new FormData(event.target))})return <form onSubmit={mutation.mutate}>...</form>}// This will workconst CreateTodo = () => {const mutation = useMutation(formData => {return fetch('/api', formData)})const onSubmit = event => {event.preventDefault()mutation.mutate(new FormData(event.target))}return <form onSubmit={onSubmit}>...</form>}
It's sometimes the case that you need to clear the error
or data
of a mutation request. To do this, you can use the reset
function to handle this:
const CreateTodo = () => {const [title, setTitle] = useState('')const mutation = useMutation(createTodo)const onCreateTodo = e => {e.preventDefault()mutation.mutate({ title })}return (<form onSubmit={onCreateTodo}>{mutation.error && (<h5 onClick={() => mutation.reset()}>{mutation.error}</h5>)}<inputtype="text"value={title}onChange={e => setTitle(e.target.value)}/><br /><button type="submit">Create Todo</button></form>)}
useMutation
comes with some helper options that allow quick and easy side-effects at any stage during the mutation lifecycle. These come in handy for both invalidating and refetching queries after mutations and even optimistic updates
useMutation(addTodo, {onMutate: variables => {// A mutation is about to happen!// Optionally return a context object with a rollback functionreturn {rollback: () => {// do some rollback logic},}},onError: (error, variables, context) => {// An error happened!if (context.rollback) {rollbac()}},onSuccess: (data, variables, context) => {// Boom baby!},onSettled: (data, error, variables, context) => {// Error or success... doesn't matter!},})
When returning a promise in any of the callback functions it will first be awaited before the next callback is called:
useMutation(addTodo, {onSuccess: async () => {console.log("I'm first!")},onSettled: async () => {console.log("I'm second!")},})
You might find that you want to add additional side-effects to some of the useMutation
lifecycle at the time of calling mutate
. To do that, you can provide any of the same callback options to the mutate
function after your mutation variable. Supported option overrides include:
onSuccess
- Will be fired after the useMutation
-level onSuccess
handleronError
- Will be fired after the useMutation
-level onError
handleronSettled
- Will be fired after the useMutation
-level onSettled
handleruseMutation(addTodo, {onSuccess: (data, variables, context) => {// I will fire first},onError: (error, variables, context) => {// I will fire first},onSettled: (data, error, variables, context) => {// I will fire first},})mutate(todo, {onSuccess: (data, variables, context) => {// I will fire second!},onError: (error, variables, context) => {// I will fire second!},onSettled: (data, error, variables, context) => {// I will fire second!},})
Use mutateAsync
instead of mutate
to get a promise which will resolve on success or throw on an error:
const mutation = useMutation(addTodo)try {const todo = await mutation.mutateAsync(todo)console.log(todo)} catch (error) {console.error(error)}
The latest TanStack news, articles, and resources, sent to your inbox.