useMutation hook is used to create, update, or delete data. Unlike queries, mutations are typically used to perform side effects on the server.
Import
import { useMutation } from '@tanstack/react-query'
Signature
function useMutation<
TData = unknown,
TError = DefaultError,
TVariables = void,
TOnMutateResult = unknown,
>(
options: UseMutationOptions<TData, TError, TVariables, TOnMutateResult>,
queryClient?: QueryClient,
): UseMutationResult<TData, TError, TVariables, TOnMutateResult>
Parameters
The mutation options object.
Show properties
Show properties
The function that performs the mutation. Must return a promise.The
context parameter contains:client: QueryClient- The QueryClient instancemeta: MutationMeta | undefined- Optional mutation metadatamutationKey?: MutationKey- The mutation key if provided
mutationFn: async (newTodo) => {
const response = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify(newTodo),
})
return response.json()
}
Optional key for the mutation. Useful for identifying mutations in devtools and for mutation caching.
mutationKey: ['createTodo']
onMutate
(variables: TVariables, context: MutationFunctionContext) => Promise<TOnMutateResult> | TOnMutateResult
Function that will fire before the mutation function is fired. Useful for optimistic updates.
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] })
const previousTodos = queryClient.getQueryData(['todos'])
queryClient.setQueryData(['todos'], old => [...old, newTodo])
return { previousTodos }
}
onSuccess
(data: TData, variables: TVariables, onMutateResult: TOnMutateResult, context: MutationFunctionContext) => Promise<unknown> | unknown
Function that will fire when the mutation is successful.
onSuccess: (data, variables, onMutateResult) => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
}
onError
(error: TError, variables: TVariables, onMutateResult: TOnMutateResult | undefined, context: MutationFunctionContext) => Promise<unknown> | unknown
Function that will fire if the mutation encounters an error.
onError: (error, variables, onMutateResult) => {
if (onMutateResult?.previousTodos) {
queryClient.setQueryData(['todos'], onMutateResult.previousTodos)
}
}
onSettled
(data: TData | undefined, error: TError | null, variables: TVariables, onMutateResult: TOnMutateResult | undefined, context: MutationFunctionContext) => Promise<unknown> | unknown
Function that will fire when the mutation is either successfully fetched or encounters an error.
onSettled: (data, error) => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
}
If
false, failed mutations will not retry. If true, failed mutations will retry infinitely. If set to a number, failed mutations will retry until the failure count meets that number.retry: 3
retry: (failureCount, error) => failureCount < 3
Delay between retries in milliseconds.
retryDelay: 1000
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000)
'online'- Mutations will not fire unless there is a network connection'always'- Mutations will fire regardless of network connection status'offlineFirst'- Mutations will fire immediately, but if they fail, they will be paused and retried when connection is restored
The time in milliseconds that unused/inactive cache data remains in memory.
Set to
true to throw errors instead of setting the error property.throwOnError: true
throwOnError: (error) => error.status >= 500
Optional metadata that can be used in other places.
meta: { operationType: 'create' }
Mutations with the same scope id will run in serial.
scope: { id: 'todo-mutations' }
Override the default QueryClient.
Returns
The mutation result object.
Show properties
Show properties
The mutation function you can call with variables to trigger the mutation.The
options parameter can override the mutation callbacks:onSuccess?: (data, variables, onMutateResult) => voidonError?: (error, variables, onMutateResult) => voidonSettled?: (data, error, variables, onMutateResult) => void
mutate({ title: 'New Todo' }, {
onSuccess: (data) => console.log('Created:', data),
})
Similar to
mutate but returns a promise which can be awaited.try {
const data = await mutateAsync({ title: 'New Todo' })
console.log('Created:', data)
} catch (error) {
console.error('Error:', error)
}
The last successfully resolved data for the mutation.
The error object for the mutation, if an error was encountered. Defaults to
null.The variables object passed to the
mutationFn.'idle'- Initial status prior to the mutation function executing'pending'- The mutation is currently executing'error'- The last mutation attempt resulted in an error'success'- The last mutation attempt was successful
true if the mutation is currently executing.true if the mutation is in its initial state prior to executing.true if the last mutation attempt was successful.true if the last mutation attempt resulted in an error.Function to clean the mutation internal state (resets to initial state).
reset()
Examples
Basic Usage
import { useMutation } from '@tanstack/react-query'
function CreateTodo() {
const mutation = useMutation({
mutationFn: async (newTodo: { title: string }) => {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newTodo),
})
if (!response.ok) throw new Error('Failed to create todo')
return response.json()
},
})
return (
<div>
{mutation.isPending && <div>Creating...</div>}
{mutation.isError && <div>Error: {mutation.error.message}</div>}
{mutation.isSuccess && <div>Todo created!</div>}
<button
onClick={() => {
mutation.mutate({ title: 'New Todo' })
}}
>
Create Todo
</button>
</div>
)
}
With Optimistic Updates
interface Todo {
id: number
title: string
}
const mutation = useMutation({
mutationFn: (newTodo: Omit<Todo, 'id'>) => createTodo(newTodo),
onMutate: async (newTodo) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['todos'] })
// Snapshot the previous value
const previousTodos = queryClient.getQueryData<Todo[]>(['todos'])
// Optimistically update to the new value
queryClient.setQueryData<Todo[]>(['todos'], (old) => [
...old,
{ ...newTodo, id: Date.now() },
])
// Return context with the snapshot
return { previousTodos }
},
onError: (err, newTodo, context) => {
// Rollback to the previous value on error
queryClient.setQueryData(['todos'], context?.previousTodos)
},
onSettled: () => {
// Always refetch after error or success
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
Using mutateAsync
const mutation = useMutation({
mutationFn: createTodo,
})
const handleSubmit = async (event: FormEvent) => {
event.preventDefault()
try {
const data = await mutation.mutateAsync({ title: 'New Todo' })
console.log('Created todo:', data)
// Navigate or perform other actions
} catch (error) {
console.error('Failed to create todo:', error)
}
}
With Callbacks
mutation.mutate(
{ title: 'New Todo' },
{
onSuccess: (data) => {
console.log('Success:', data)
},
onError: (error) => {
console.error('Error:', error)
},
onSettled: () => {
console.log('Mutation finished')
},
}
)
Serial Mutations with Scope
const mutation1 = useMutation({
mutationFn: createTodo,
scope: { id: 'todo-mutations' },
})
const mutation2 = useMutation({
mutationFn: updateTodo,
scope: { id: 'todo-mutations' }, // Same scope = runs serially
})
Notes
Unlike
useQuery, mutations do not automatically execute. You must call mutate or mutateAsync to trigger them.The
mutate function does not return the mutation result. Use mutateAsync if you need to await the result.Callbacks passed to
mutate will override the callbacks defined in the hook options for that specific mutation call.