Skip to main content
The 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

options
object
required
The mutation options object.
queryClient
QueryClient
Override the default QueryClient.

Returns

UseMutationResult<TData, TError, TVariables, TOnMutateResult>
object
The mutation result object.

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.

Build docs developers (and LLMs) love