Skip to main content

mutationOptions

The mutationOptions helper provides type-safe mutation options with proper type inference. It’s a lightweight identity function that helps TypeScript infer types correctly and provides better autocomplete for mutations.

Import

import { mutationOptions } from '@tanstack/react-query'

Signature

function mutationOptions<
  TData = unknown,
  TError = DefaultError,
  TVariables = void,
  TOnMutateResult = unknown,
>(
  options: UseMutationOptions<TData, TError, TVariables, TOnMutateResult>
): UseMutationOptions<TData, TError, TVariables, TOnMutateResult>

Type Parameters

TData
type
default:"unknown"
The type of data returned by the mutation function
TError
type
default:"DefaultError"
The type of error that can be thrown by the mutation function
TVariables
type
default:"void"
The type of variables passed to the mutation function
TOnMutateResult
type
default:"unknown"
The type of context returned by the onMutate callback

Parameters

options
UseMutationOptions
required
Mutation configuration options. Accepts all options that useMutation accepts.

Returns

Returns the same options object that was passed in, with enhanced type information for better TypeScript inference.

Examples

Basic Usage

import { mutationOptions, useMutation } from '@tanstack/react-query'

const createPostOptions = mutationOptions({
  mutationFn: (newPost: { title: string; body: string }) => {
    return fetch('/api/posts', {
      method: 'POST',
      body: JSON.stringify(newPost),
    }).then(res => res.json())
  },
})

function CreatePost() {
  const mutation = useMutation(createPostOptions)
  
  return (
    <button onClick={() => mutation.mutate({ title: 'Hello', body: 'World' })}>
      Create Post
    </button>
  )
}

Reusable Mutation Definitions

import { mutationOptions, useMutation } from '@tanstack/react-query'

interface Post {
  id: number
  title: string
  body: string
}

const postMutations = {
  create: mutationOptions({
    mutationFn: async (newPost: Omit<Post, 'id'>): Promise<Post> => {
      const res = await fetch('/api/posts', {
        method: 'POST',
        body: JSON.stringify(newPost),
      })
      return res.json()
    },
  }),
  update: (id: number) => mutationOptions({
    mutationFn: async (updatedPost: Partial<Post>): Promise<Post> => {
      const res = await fetch(`/api/posts/${id}`, {
        method: 'PATCH',
        body: JSON.stringify(updatedPost),
      })
      return res.json()
    },
  }),
  delete: (id: number) => mutationOptions({
    mutationFn: async (): Promise<void> => {
      await fetch(`/api/posts/${id}`, { method: 'DELETE' })
    },
  }),
}

// Use in components
function CreatePost() {
  const mutation = useMutation(postMutations.create)
  return <button onClick={() => mutation.mutate({ title: '', body: '' })}>
    Create
  </button>
}

function EditPost({ id }: { id: number }) {
  const mutation = useMutation(postMutations.update(id))
  return <button onClick={() => mutation.mutate({ title: 'Updated' })}>
    Update
  </button>
}

With Mutation Key

import { mutationOptions, useMutation } from '@tanstack/react-query'

const createPostOptions = mutationOptions({
  mutationKey: ['posts', 'create'],
  mutationFn: (newPost: { title: string; body: string }) => {
    return createPost(newPost)
  },
})

function CreatePost() {
  const mutation = useMutation(createPostOptions)
  return <div>{/* ... */}</div>
}

With Optimistic Updates

import {
  mutationOptions,
  useMutation,
  useQueryClient,
} from '@tanstack/react-query'

interface Todo {
  id: number
  text: string
  done: boolean
}

const updateTodoOptions = mutationOptions({
  mutationFn: async (todo: Todo): Promise<Todo> => {
    const res = await fetch(`/api/todos/${todo.id}`, {
      method: 'PUT',
      body: JSON.stringify(todo),
    })
    return res.json()
  },
  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) => {
      return old?.map((todo) =>
        todo.id === newTodo.id ? newTodo : todo
      )
    })

    // Return context with the previous value
    return { previousTodos }
  },
  onError: (err, newTodo, context) => {
    // Rollback on error
    queryClient.setQueryData(['todos'], context?.previousTodos)
  },
  onSettled: () => {
    // Refetch after error or success
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})

function UpdateTodo({ todo }: { todo: Todo }) {
  const queryClient = useQueryClient()
  const mutation = useMutation(updateTodoOptions)
  
  return (
    <button onClick={() => mutation.mutate({ ...todo, done: !todo.done })}>
      Toggle
    </button>
  )
}

With Type Inference

import { mutationOptions, useMutation } from '@tanstack/react-query'

interface User {
  id: number
  name: string
  email: string
}

const registerUserOptions = mutationOptions({
  mutationFn: async (userData: {
    name: string
    email: string
    password: string
  }): Promise<User> => {
    const res = await fetch('/api/register', {
      method: 'POST',
      body: JSON.stringify(userData),
    })
    return res.json()
  },
})

function RegisterForm() {
  const mutation = useMutation(registerUserOptions)
  
  // TypeScript knows:
  // - mutation.data is User | undefined
  // - mutation.variables is the userData type
  // - mutation.error is DefaultError
  
  return <div>{mutation.data?.name}</div>
}

Factory Pattern

import { mutationOptions } from '@tanstack/react-query'

interface ApiResource<T> {
  data: T
  message: string
}

function createMutationFactory<T>(endpoint: string) {
  return {
    create: mutationOptions({
      mutationFn: async (data: Partial<T>): Promise<ApiResource<T>> => {
        const res = await fetch(`/api/${endpoint}`, {
          method: 'POST',
          body: JSON.stringify(data),
        })
        return res.json()
      },
    }),
    update: (id: number) => mutationOptions({
      mutationFn: async (data: Partial<T>): Promise<ApiResource<T>> => {
        const res = await fetch(`/api/${endpoint}/${id}`, {
          method: 'PATCH',
          body: JSON.stringify(data),
        })
        return res.json()
      },
    }),
    delete: (id: number) => mutationOptions({
      mutationFn: async (): Promise<void> => {
        await fetch(`/api/${endpoint}/${id}`, { method: 'DELETE' })
      },
    }),
  }
}

interface Post {
  id: number
  title: string
  body: string
}

const postMutations = createMutationFactory<Post>('posts')

function CreatePost() {
  const mutation = useMutation(postMutations.create)
  return <div>{/* ... */}</div>
}

Notes

  • mutationOptions is an identity function that returns the exact object passed to it.
  • Its primary purpose is to improve TypeScript type inference and provide better autocomplete.
  • It’s particularly useful when you want to share mutation configurations across multiple components or files.
  • Unlike queryOptions, the mutationKey is optional for mutations.
  • This pattern is recommended for organizing and reusing mutation configurations in larger applications.
  • The helper supports all options available in useMutation, including callbacks like onSuccess, onError, and onMutate.

Build docs developers (and LLMs) love