Skip to main content

MutationObserver

The MutationObserver class is used to observe mutations and subscribe to changes in mutation state. This is the core class that powers useMutation in framework integrations.

Import

import { MutationObserver } from '@tanstack/query-core'

Constructor

class MutationObserver<
  TData = unknown,
  TError = DefaultError,
  TVariables = void,
  TContext = unknown,
> {
  constructor(
    client: QueryClient,
    options: MutationObserverOptions<TData, TError, TVariables, TContext>
  )
}

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
TVariables
type
default:"void"
The type of variables passed to the mutation function
TContext
type
default:"unknown"
The type of context returned by onMutate

Parameters

client
QueryClient
required
The QueryClient instance to use
options
MutationObserverOptions
required
Configuration options for the mutation observer
mutationFn
(variables: TVariables) => Promise<TData>
required
The function that performs the mutation
mutationKey
MutationKey
Optional unique key for the mutation
onMutate
(variables: TVariables) => Promise<TContext> | TContext
Callback fired before mutation function is executed
onSuccess
(data: TData, variables: TVariables, context: TContext) => Promise<unknown> | unknown
Callback fired when mutation succeeds
onError
(error: TError, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown
Callback fired when mutation errors
onSettled
(data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise<unknown> | unknown
Callback fired when mutation completes (success or error)
retry
boolean | number | (failureCount: number, error: TError) => boolean
default:"0"
Number of retry attempts or function to determine retries
retryDelay
number | (failureCount: number, error: TError) => number
Delay between retry attempts

Methods

mutate

mutate(
  variables: TVariables,
  options?: MutateOptions<TData, TError, TVariables, TContext>
): Promise<TData>
Execute the mutation with the given variables.

reset

reset(): void
Reset the mutation observer to its initial state.

setOptions

setOptions(
  options: MutationObserverOptions<TData, TError, TVariables, TContext>
): void
Update the mutation observer options.

subscribe

subscribe(
  listener: (result: MutationObserverResult<TData, TError, TVariables, TContext>) => void
): () => void
Subscribe to mutation state changes. Returns an unsubscribe function.

getCurrentResult

getCurrentResult(): MutationObserverResult<TData, TError, TVariables, TContext>
Get the current mutation result.

Result Type

interface MutationObserverResult<TData, TError, TVariables, TContext> {
  context: TContext | undefined
  data: TData | undefined
  error: TError | null
  failureCount: number
  failureReason: TError | null
  isError: boolean
  isIdle: boolean
  isPending: boolean
  isPaused: boolean
  isSuccess: boolean
  mutate: (variables: TVariables, options?: MutateOptions) => void
  mutateAsync: (variables: TVariables, options?: MutateOptions) => Promise<TData>
  reset: () => void
  status: 'idle' | 'pending' | 'error' | 'success'
  submittedAt: number
  variables: TVariables | undefined
}

Examples

Basic Usage

import { MutationObserver, QueryClient } from '@tanstack/query-core'

const queryClient = new QueryClient()

const observer = new MutationObserver(queryClient, {
  mutationFn: async (newPost) => {
    const response = await fetch('/api/posts', {
      method: 'POST',
      body: JSON.stringify(newPost),
    })
    return response.json()
  },
})

// Subscribe to changes
const unsubscribe = observer.subscribe((result) => {
  console.log('Mutation state:', result)
})

// Execute mutation
observer.mutate({ title: 'Hello', body: 'World' })

// Clean up
unsubscribe()

With Callbacks

import { MutationObserver, QueryClient } from '@tanstack/query-core'

const queryClient = new QueryClient()

const observer = new MutationObserver(queryClient, {
  mutationFn: updatePost,
  onMutate: async (variables) => {
    // Cancel outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['posts'] })
    
    // Snapshot previous value
    const previous = queryClient.getQueryData(['posts'])
    
    // Optimistically update
    queryClient.setQueryData(['posts'], (old) => {
      return old.map((post) =>
        post.id === variables.id ? { ...post, ...variables } : post
      )
    })
    
    return { previous }
  },
  onError: (err, variables, context) => {
    // Rollback on error
    queryClient.setQueryData(['posts'], context.previous)
  },
  onSettled: () => {
    // Refetch after mutation
    queryClient.invalidateQueries({ queryKey: ['posts'] })
  },
})

Multiple Subscribers

import { MutationObserver } from '@tanstack/query-core'

const observer = new MutationObserver(queryClient, {
  mutationFn: createPost,
})

// Multiple components can subscribe
const unsubscribe1 = observer.subscribe((result) => {
  console.log('Subscriber 1:', result.status)
})

const unsubscribe2 = observer.subscribe((result) => {
  console.log('Subscriber 2:', result.status)
})

// Both subscribers will be notified
observer.mutate(newPost)

// Clean up
unsubscribe1()
unsubscribe2()

Updating Options

import { MutationObserver } from '@tanstack/query-core'

const observer = new MutationObserver(queryClient, {
  mutationFn: createPost,
})

// Update options later
observer.setOptions({
  mutationFn: createPost,
  onSuccess: (data) => {
    console.log('Post created:', data)
  },
})

Manual State Management

import { MutationObserver } from '@tanstack/query-core'

const observer = new MutationObserver(queryClient, {
  mutationFn: updateUser,
})

// Get current state
const result = observer.getCurrentResult()
console.log('Status:', result.status)
console.log('Data:', result.data)
console.log('Error:', result.error)

// Reset to initial state
observer.reset()

With Retry Logic

import { MutationObserver } from '@tanstack/query-core'

const observer = new MutationObserver(queryClient, {
  mutationFn: createPost,
  retry: 3,
  retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
})

Notes

  • MutationObserver is the low-level class that framework integrations build upon
  • In React, useMutation creates and manages a MutationObserver instance
  • Observers can have multiple subscribers
  • The observer manages a single mutation instance at a time
  • Calling mutate() multiple times creates new mutation instances
  • Each mutation instance has its own state and lifecycle
  • The observer automatically handles retry logic based on configuration
  • Subscribers are notified synchronously when state changes

Build docs developers (and LLMs) love