Skip to main content
Perform mutations and side effects with the useMutation composable. It provides methods to trigger mutations and tracks their state reactively.

Signature

function useMutation<TData, TError, TVariables, TContext>(
  options: UseMutationOptions<TData, TError, TVariables, TContext>,
  queryClient?: QueryClient,
): UseMutationReturnType<TData, TError, TVariables, TContext>

Parameters

options
UseMutationOptions<TData, TError, TVariables, TContext>
required
Configuration options for the mutation. Can be a reactive ref or getter function.
queryClient
QueryClient
Custom QueryClient instance. If not provided, uses the client from context.

Returns

UseMutationReturnType<TData, TError, TVariables, TContext>
object
Reactive refs containing mutation state and methods.

Type Parameters

  • TData - Type of data returned by the mutation
  • TError - Type of error (defaults to DefaultError)
  • TVariables - Type of variables passed to the mutation (defaults to void)
  • TContext - Type of context returned by onMutate (defaults to unknown)

Examples

Basic Usage

<script setup>
import { useMutation, useQueryClient } from '@tanstack/vue-query'

const queryClient = useQueryClient()

const { mutate, isPending, isError, error } = useMutation({
  mutationFn: async (newTodo) => {
    const res = await fetch('/api/todos', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(newTodo),
    })
    return res.json()
  },
  onSuccess: () => {
    // Invalidate and refetch
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})

const addTodo = () => {
  mutate({ title: 'New Todo', completed: false })
}
</script>

<template>
  <div>
    <button @click="addTodo" :disabled="isPending">
      {{ isPending ? 'Adding...' : 'Add Todo' }}
    </button>
    <div v-if="isError">Error: {{ error.message }}</div>
  </div>
</template>

With TypeScript

<script setup lang="ts">
import { useMutation } from '@tanstack/vue-query'

interface Todo {
  id: number
  title: string
  completed: boolean
}

interface CreateTodoVariables {
  title: string
  completed: boolean
}

const { mutate } = useMutation({
  mutationFn: async (variables: CreateTodoVariables): Promise<Todo> => {
    const res = await fetch('/api/todos', {
      method: 'POST',
      body: JSON.stringify(variables),
    })
    return res.json()
  },
})

// Type-safe mutation call
mutate({ title: 'Learn Vue Query', completed: false })
</script>

Optimistic Updates

<script setup>
import { useMutation, useQueryClient } from '@tanstack/vue-query'

const queryClient = useQueryClient()

const { mutate } = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['todos'] })
    
    // Snapshot previous value
    const previousTodos = queryClient.getQueryData(['todos'])
    
    // Optimistically update cache
    queryClient.setQueryData(['todos'], (old) => {
      return old.map(todo => 
        todo.id === newTodo.id ? newTodo : todo
      )
    })
    
    // Return context with snapshot
    return { previousTodos }
  },
  onError: (err, newTodo, context) => {
    // Rollback on error
    queryClient.setQueryData(['todos'], context.previousTodos)
  },
  onSettled: () => {
    // Always refetch after error or success
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})
</script>

Using mutateAsync

<script setup>
import { useMutation } from '@tanstack/vue-query'
import { ref } from 'vue'

const { mutateAsync, isPending } = useMutation({
  mutationFn: createTodo,
})

const handleSubmit = async () => {
  try {
    const newTodo = await mutateAsync({ title: 'New Todo' })
    console.log('Created todo:', newTodo)
    // Navigate or show success message
  } catch (error) {
    console.error('Failed to create todo:', error)
  }
}
</script>

Per-Mutation Callbacks

<script setup>
import { useMutation } from '@tanstack/vue-query'

const { mutate } = useMutation({
  mutationFn: createTodo,
})

// Pass callbacks per mutation call
const handleCreate = () => {
  mutate(
    { title: 'New Todo' },
    {
      onSuccess: (data) => {
        console.log('Created:', data)
      },
      onError: (error) => {
        console.error('Failed:', error)
      },
    }
  )
}
</script>

Reactive Mutation Options

<script setup>
import { ref, computed } from 'vue'
import { useMutation } from '@tanstack/vue-query'

const retryCount = ref(3)

// Options can be reactive
const mutationOptions = computed(() => ({
  mutationFn: createTodo,
  retry: retryCount.value,
}))

const { mutate } = useMutation(mutationOptions)
</script>

Reset Mutation State

<script setup>
import { useMutation } from '@tanstack/vue-query'

const { mutate, reset, isSuccess, error } = useMutation({
  mutationFn: createTodo,
})

const handleCreate = () => {
  // Reset previous state before new mutation
  reset()
  mutate({ title: 'New Todo' })
}
</script>

Build docs developers (and LLMs) love