
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 to the server:
function App() {const mutation = useMutation(newTodo => {return axios.post('/todos', newTodo)})return (<div>{mutation.isLoading ? ('Adding todo...') : (<>{mutation.isError ? (<div>An error occurred: {mutation.error.message}</div>) : null}{mutation.isSuccess ? <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 states, more information is available depending on the state of the mutation:
error - If the mutation is in an error 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
mutatefunction is an asynchronous function, which means you cannot use it directly in an event callback in React 16 and earlier. If you need to access the event inonSubmityou need to wrapmutatein another function. This is due to React event pooling.
// This will not work in React 16 and earlierconst 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 containing data to use when for example rolling backreturn { id: 1 }},onError: (error, variables, context) => {// An error happened!console.log(`rolling back optimistic update with id ${context.id}`)},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 trigger additional callbacks than the ones defined on useMutation when calling mutate. This can be used to trigger component-specific side effects. To do that, you can provide any of the same callback options to the mutate function after your mutation variable. Supported overrides include: onSuccess, onError and onSettled. Please keep in mind that those additional callbacks won't run if your component unmounts before the mutation finishes.
useMutation(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. This can for example be used to compose side effects.
const mutation = useMutation(addTodo)try {const todo = await mutation.mutateAsync(todo)console.log(todo)} catch (error) {console.error(error)} finally {console.log('done')}
By default React Query will not retry a mutation on error, but it is possible with the retry option:
const mutation = useMutation(addTodo, {retry: 3,})
If mutations fail because the device is offline, they will be retried in the same order when the device reconnects.
Mutations can be persisted to storage if needed and resumed at a later point. This can be done with the hydration functions:
const queryClient = new QueryClient()// Define the "addTodo" mutationqueryClient.setMutationDefaults('addTodo', {mutationFn: addTodo,onMutate: async (variables) => {// Cancel current queries for the todos listawait queryClient.cancelQueries('todos')// Create optimistic todoconst optimisticTodo = { id: uuid(), title: variables.title }// Add optimistic todo to todos listqueryClient.setQueryData('todos', old => [...old, optimisticTodo])// Return context with the optimistic todoreturn { optimisticTodo }},onSuccess: (result, variables, context) => {// Replace optimistic todo in the todos list with the resultqueryClient.setQueryData('todos', old => old.map(todo => todo.id === context.optimisticTodo.id ? result : todo))},onError: (error, variables, context) => {// Remove optimistic todo from the todos listqueryClient.setQueryData('todos', old => old.filter(todo => todo.id !== context.optimisticTodo.id))},retry: 3,})// Start mutation in some component:const mutation = useMutation('addTodo')mutation.mutate({ title: 'title' })// If the mutation has been paused because the device is for example offline,// Then the paused mutation can be dehydrated when the application quits:const state = dehydrate(queryClient)// The mutation can then be hydrated again when the application is started:hydrate(queryClient, state)// Resume the paused mutations:queryClient.resumePausedMutations()
For more information about mutations, have a look at #12: Mastering Mutations in React Query from the Community Resources.
The latest TanStack news, articles, and resources, sent to your inbox.