Overview
The MutationObserver class is used to subscribe to a mutation and receive reactive updates when the mutation’s state changes. It’s the foundation for framework-specific hooks like useMutation in React.
Constructor
const observer = new MutationObserver < TData , TError , TVariables , TOnMutateResult >(
client : QueryClient ,
options : MutationObserverOptions < TData , TError , TVariables , TOnMutateResult >
)
options
MutationObserverOptions
required
Options for the mutation observer mutationFn
(variables: TVariables, context: MutationFunctionContext) => Promise<TData>
The function to execute the mutation
Unique key for the mutation
onMutate
(variables: TVariables, context: MutationFunctionContext) => Promise<TOnMutateResult> | TOnMutateResult
Callback fired before the mutation function. Return value is passed to other callbacks.
onSuccess
(data: TData, variables: TVariables, onMutateResult: TOnMutateResult, context: MutationFunctionContext) => Promise<unknown> | unknown
Callback fired when the mutation succeeds
onError
(error: TError, variables: TVariables, onMutateResult: TOnMutateResult | undefined, context: MutationFunctionContext) => Promise<unknown> | unknown
Callback fired when the mutation errors
onSettled
(data: TData | undefined, error: TError | null, variables: TVariables, onMutateResult: TOnMutateResult | undefined, context: MutationFunctionContext) => Promise<unknown> | unknown
Callback fired when the mutation completes (success or error)
retry
boolean | number | ((failureCount: number, error: TError) => boolean)
Retry failed mutations
retryDelay
number | ((failureCount: number, error: TError) => number)
Delay between retry attempts in milliseconds
networkMode
'online' | 'always' | 'offlineFirst'
Network mode for the mutation. Defaults to 'online'.
Time in milliseconds that unused mutation results remain in memory
Scope for sequential mutation execution
Example:
const observer = new MutationObserver ( queryClient , {
mutationFn : async ( newTodo ) => {
const response = await fetch ( '/api/todos' , {
method: 'POST' ,
body: JSON . stringify ( newTodo ),
})
return response . json ()
},
onSuccess : ( data ) => {
console . log ( 'Todo created:' , data )
},
})
Methods
subscribe()
Subscribes to mutation updates.
const unsubscribe = observer . subscribe (
listener : ( result : MutationObserverResult < TData , TError , TVariables , TOnMutateResult >) => void
): () => void
listener
(result: MutationObserverResult<TData, TError, TVariables, TOnMutateResult>) => void
required
Callback function that receives mutation results
Returns: Function to unsubscribe
Example:
const unsubscribe = observer . subscribe (( result ) => {
console . log ( 'Status:' , result . status )
console . log ( 'Data:' , result . data )
console . log ( 'Is pending:' , result . isPending )
})
// Later, unsubscribe
unsubscribe ()
getCurrentResult()
Returns the current result without subscribing.
const result = observer . getCurrentResult (): MutationObserverResult < TData , TError , TVariables , TOnMutateResult >
Returns: Current mutation result
The data returned from the mutation
The error object if mutation failed
The variables passed to the mutation function
context
TOnMutateResult | undefined
The value returned from onMutate
status
'idle' | 'pending' | 'success' | 'error'
The status of the mutation
Is true when the mutation is currently executing
Is true when the mutation has succeeded
Is true when the mutation has failed
Is true when the mutation is in its initial state
mutate
(variables: TVariables, options?: MutateOptions) => Promise<TData>
Function to trigger the mutation
Function to reset the mutation to its initial state
setOptions()
Updates the observer’s options.
observer . setOptions (
options : MutationObserverOptions < TData , TError , TVariables , TOnMutateResult >
): void
options
MutationObserverOptions
required
New options for the observer
Example:
observer . setOptions ({
mutationFn: updatedMutationFn ,
onSuccess : ( data ) => {
console . log ( 'Success with new callback:' , data )
},
})
mutate()
Executes the mutation.
const promise = observer . mutate (
variables : TVariables ,
options ?: MutateOptions < TData , TError , TVariables , TOnMutateResult >
): Promise < TData >
Variables to pass to the mutation function
Additional options for this specific mutation call onSuccess
(data: TData, variables: TVariables, onMutateResult: TOnMutateResult | undefined, context: MutationFunctionContext) => void
Callback for this specific mutation success
onError
(error: TError, variables: TVariables, onMutateResult: TOnMutateResult | undefined, context: MutationFunctionContext) => void
Callback for this specific mutation error
onSettled
(data: TData | undefined, error: TError | null, variables: TVariables, onMutateResult: TOnMutateResult | undefined, context: MutationFunctionContext) => void
Callback for this specific mutation completion
Returns: Promise that resolves with the mutation data or rejects with the error
Example:
try {
const data = await observer . mutate (
{ title: 'New Todo' , completed: false },
{
onSuccess : ( data ) => {
console . log ( 'This todo was created:' , data )
},
}
)
console . log ( 'Created:' , data )
} catch ( error ) {
console . error ( 'Failed to create todo:' , error )
}
reset()
Resets the mutation to its initial idle state.
Example:
Resetting removes the observer from the current mutation. A new call to mutate() will create a new mutation instance.
Properties
options
The current options for the observer.
observer . options : MutationObserverOptions < TData , TError , TVariables , TOnMutateResult >
Type Parameters
TData - The type of data returned by the mutation function
TError - The type of error that can be thrown (defaults to DefaultError)
TVariables - The type of variables passed to the mutation function (defaults to void)
TOnMutateResult - The type of value returned by the onMutate callback
Usage Example
import { QueryClient , MutationObserver } from '@tanstack/query-core'
const queryClient = new QueryClient ()
interface Todo {
id : number
title : string
completed : boolean
}
interface NewTodo {
title : string
completed : boolean
}
const observer = new MutationObserver < Todo , Error , NewTodo >(
queryClient ,
{
mutationFn : async ( newTodo ) => {
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 ()
},
onMutate : async ( newTodo ) => {
// Optimistically update the UI
const previousTodos = queryClient . getQueryData ([ 'todos' ])
queryClient . setQueryData ([ 'todos' ], ( old ) => [ ... old , newTodo ])
return { previousTodos }
},
onError : ( error , newTodo , context ) => {
// Rollback on error
if ( context ?. previousTodos ) {
queryClient . setQueryData ([ 'todos' ], context . previousTodos )
}
},
onSuccess : ( data ) => {
// Invalidate and refetch
queryClient . invalidateQueries ({ queryKey: [ 'todos' ] })
},
}
)
// Subscribe to updates
const unsubscribe = observer . subscribe (( result ) => {
if ( result . isPending ) {
console . log ( 'Creating todo...' )
} else if ( result . isError ) {
console . error ( 'Error:' , result . error )
} else if ( result . isSuccess ) {
console . log ( 'Todo created:' , result . data )
}
})
// Execute the mutation
try {
const newTodo = await observer . mutate ({
title: 'Learn TanStack Query' ,
completed: false ,
})
console . log ( 'Created todo:' , newTodo )
} catch ( error ) {
console . error ( 'Failed:' , error )
}
// Later, clean up
unsubscribe ()
Optimistic Updates
The MutationObserver is commonly used for optimistic updates:
const observer = new MutationObserver ( queryClient , {
mutationFn: updateTodo ,
onMutate : async ( updatedTodo ) => {
// Cancel outgoing refetches
await queryClient . cancelQueries ({ queryKey: [ 'todos' , updatedTodo . id ] })
// Snapshot the previous value
const previousTodo = queryClient . getQueryData ([ 'todos' , updatedTodo . id ])
// Optimistically update
queryClient . setQueryData ([ 'todos' , updatedTodo . id ], updatedTodo )
// Return context with the snapshot
return { previousTodo }
},
onError : ( error , updatedTodo , context ) => {
// Rollback to the previous value
if ( context ?. previousTodo ) {
queryClient . setQueryData ([ 'todos' , updatedTodo . id ], context . previousTodo )
}
},
onSettled : ( data , error , updatedTodo ) => {
// Always refetch after error or success
queryClient . invalidateQueries ({ queryKey: [ 'todos' , updatedTodo . id ] })
},
})