new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

TanStack Query · React · all subjects

mutations

89 notes in this subject, read out of this brain and free to use. This is page 1 of 2.

Other mutation callbacks can be used for invalidations

In addition to onSuccess, invalidations can be wired up using any of the other callbacks available in the useMutation hook.

Invalidate queries after mutation success with onSuccess

When a mutation succeeds, use the useMutation hook's onSuccess callback with queryClient.invalidateQueries() to invalidate related queries. This ensures queries are refetched to reflect changes from the mutation.

Single query invalidation with invalidateQueries

To invalidate a single query after mutation success, call queryClient.invalidateQueries({ queryKey: ['queryKeyName'] }) inside the onSuccess callback.

Multiple queries invalidation after mutation

To invalidate multiple queries after mutation success, use Promise.all() with multiple queryClient.invalidateQueries() calls for each queryKey: await Promise.all([queryClient.invalidateQueries({ queryKey: ['todos'] }), queryClient.invalidateQueries({ queryKey: ['reminders'] })])

Returning Promise in onSuccess ensures data updates before mutation completion

Returning a Promise in the onSuccess callback ensures that data is updated before the mutation is entirely complete. The isPending flag remains true until the returned Promise is fulfilled, preventing the mutation from being marked as complete until invalidation is finished.

Use useQueryClient hook to access query client in components

Import and call useQueryClient() hook to get access to the queryClient instance for invalidating queries inside useMutation callbacks.

Mutation retry option for offline support

In React Query v3, mutations can be retried on error using the retry option. Example: const mutation = useMutation({ mutationFn: addTodo, retry: 3 }). If mutations fail because the device is offline, they will be retried in the same order when the device reconnects.

useMutation returns object instead of array

In React Query v3, useMutation now returns an object instead of an array. Change from const [mutate, { status, reset }] = useMutation() to const { mutate, status, reset } = useMutation()

mutation.mutate does not return promise; use mutation.mutateAsync

In React Query v3, mutation.mutate function no longer returns a promise. Use mutation.mutateAsync for async/await patterns and mutation.mutate for callback patterns. Example with callbacks: const { mutate } = useMutation({ mutationFn: addTodo }); mutate('todo', { onSuccess: (data) => {...}, onError: (error) => {...}, onSettled: () => {...} }). Example with async/await: const { mutateAsync } = useMutation({ mutationFn: addTodo }); const data = await mutateAsync('todo')

QueryClient.setMutationDefaults() for mutation defaults

In React Query v3, QueryClient.setMutationDefaults() method sets default options for specific mutations. Example: queryClient.setMutationDefaults(['addPost'], { mutationFn: addPost }); then useMutation({ mutationKey: ['addPost'] }) will use those defaults.

Reset mutation example

Example showing how to use mutation.reset() to clear error state: ```tsx const CreateTodo = () => { const [title, setTitle] = useState('') const mutation = useMutation({ mutationFn: createTodo }) const onCreateTodo = (e) => { e.preventDefault() mutation.mutate({ title }) } return ( <form onSubmit={onCreateTodo}> {mutation.error && ( <h5 onClick={() => mutation.reset()}>{mutation.error}</h5> )} <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} /> <br /> <button type="submit">Create Todo</button> </form> ) } ```

Mutation side effect callbacks

useMutation supports the following callback options for side effects: onMutate (called before mutation starts), onError (called on error), onSuccess (called on success), and onSettled (called whether error or success).

Mutation callback lifecycle example

Example showing onMutate, onError, onSuccess, and onSettled callbacks: ```tsx useMutation({ mutationFn: addTodo, onMutate: (variables, context) => { // A mutation is about to happen! // Optionally return a result containing data to use when for example rolling back return { id: 1 } }, onError: (error, variables, onMutateResult, context) => { // An error happened! console.log(`rolling back optimistic update with id ${onMutateResult.id}`) }, onSuccess: (data, variables, onMutateResult, context) => { // Boom baby! }, onSettled: (data, error, variables, onMutateResult, context) => { // Error or success... doesn't matter! }, }) ```

Awaiting promises in mutation callbacks

When a promise is returned in any callback function (onMutate, onError, onSuccess, onSettled), it will be awaited before the next callback is called, allowing for sequential callback execution.

Promise callback ordering example

Example showing that promises in callbacks are awaited before the next callback executes: ```tsx useMutation({ mutationFn: addTodo, onSuccess: async () => { console.log("I'm first!") }, onSettled: async () => { console.log("I'm second!") }, }) ```

Additional callbacks passed to mutate function

Component-specific side effects can be triggered by passing callback options (onSuccess, onError, onSettled) to the mutate function after the mutation variable. These callbacks will not run if the component unmounts before the mutation finishes.

Mutate function callbacks example

Example showing how to pass callbacks to the mutate function, which execute after useMutation callbacks: ```tsx useMutation({ mutationFn: addTodo, onSuccess: (data, variables, onMutateResult, context) => { // I will fire first }, onError: (error, variables, onMutateResult, context) => { // I will fire first }, onSettled: (data, error, variables, onMutateResult, context) => { // I will fire first }, }) mutate(todo, { onSuccess: (data, variables, onMutateResult, context) => { // I will fire second! }, onError: (error, variables, onMutateResult, context) => { // I will fire second! }, onSettled: (data, error, variables, onMutateResult, context) => { // I will fire second! }, }) ```

Consecutive mutations callback behavior

When callbacks are passed to the mutate function during consecutive mutations, they fire only once and only if the component is still mounted. This occurs because the mutation observer is removed and resubscribed each time mutate is called. In contrast, useMutation handlers execute for each mutate call.

Consecutive mutations example

Example showing that mutate callbacks fire only once for the last mutation in consecutive calls, while useMutation callbacks fire for each call: ```tsx useMutation({ mutationFn: addTodo, onSuccess: (data, variables, onMutateResult, context) => { // Will be called 3 times }, }) const todos = ['Todo 1', 'Todo 2', 'Todo 3'] todos.forEach((todo) => { mutate(todo, { onSuccess: (data, variables, onMutateResult, context) => { // Will execute only once, for the last mutation (Todo 3), // regardless which mutation resolves first }, }) }) ```

Async mutation ordering caveat

Be aware that if mutationFn is asynchronous, the order in which mutations are fulfilled may differ from the order of mutate function calls.

mutateAsync for promise handling

Use mutateAsync instead of mutate to get a promise which resolves on success or throws on error. This can be used to compose side effects with try/catch.

mutateAsync example

Example showing how to use mutateAsync with try/catch: ```tsx const mutation = useMutation({ mutationFn: addTodo }) try { const todo = await mutation.mutateAsync(todo) console.log(todo) } catch (error) { console.error(error) } finally { console.log('done') } ```

Mutation retry option

By default, TanStack Query will not retry a mutation on error, but retries can be enabled with the retry option, which accepts a number indicating how many times to retry.

Mutation retry example

Example showing how to enable retry for mutations: ```tsx const mutation = useMutation({ mutationFn: addTodo, retry: 3, }) ```

Offline mutation retry behavior

If mutations fail because the device is offline, they will be retried in the same order when the device reconnects.

Mutation persistence with hydration

Mutations can be persisted to storage and resumed at a later point using hydration functions. Mutations can be defined with setMutationDefaults, persisted with dehydrate, and resumed with hydrate and resumePausedMutations.

Persist mutations example

Example showing how to persist and resume mutations using hydration: ```tsx const queryClient = new QueryClient() // Define the "addTodo" mutation queryClient.setMutationDefaults(['addTodo'], { mutationFn: addTodo, onMutate: async (variables, context) => { // Cancel current queries for the todos list await context.client.cancelQueries({ queryKey: ['todos'] }) // Create optimistic todo const optimisticTodo = { id: uuid(), title: variables.title } // Add optimistic todo to todos list context.client.setQueryData(['todos'], (old) => [...old, optimisticTodo]) // Return a result with the optimistic todo return { optimisticTodo } }, onSuccess: (result, variables, onMutateResult, context) => { // Replace optimistic todo in the todos list with the result context.client.setQueryData(['todos'], (old) => old.map((todo) => todo.id === onMutateResult.optimisticTodo.id ? result : todo, ), ) }, onError: (error, variables, onMutateResult, context) => { // Remove optimistic todo from the todos list context.client.setQueryData(['todos'], (old) => old.filter((todo) => todo.id !== onMutateResult.optimisticTodo.id), ) }, retry: 3, }) // Start mutation in some component: const mutation = useMutation({ mutationKey: ['addTodo'] }) mutation.mutate({ title: 'title' }) // If the mutation has been paused because the device is for example offline, // Then the paused mutation can be dehydrated when the application quits: const state = dehydrate(queryClient) // The mutation can then be hydrated again when the application is started: hydrate(queryClient, state) // Resume the paused mutations: queryClient.resumePausedMutations() ```

Persisting offline mutations limitation

When persisting offline mutations with the persistQueryClient plugin, mutations cannot be resumed after page reload unless a default mutation function is provided. This is because only the state of mutations is persisted (functions cannot be serialized), and the component triggering the mutation might not be mounted after hydration, potentially causing a 'No mutationFn found' error.

Offline mutation persistence example

Example showing how to provide default mutation functions for offline persistence: ```tsx const persister = createSyncStoragePersister({ storage: window.localStorage, }) const queryClient = new QueryClient({ defaultOptions: { queries: { gcTime: 1000 * 60 * 60 * 24, // 24 hours }, }, }) // we need a default mutation function so that paused mutations can resume after a page reload queryClient.setMutationDefaults(['todos'], { mutationFn: ({ id, data }) => { return api.updateTodo(id, data) }, }) export default function App() { return ( <PersistQueryClientProvider client={queryClient} persistOptions={{ persister }} onSuccess={() => { // resume mutations after initial restore from localStorage was successful queryClient.resumePausedMutations() }} > <RestOfTheApp /> </PersistQueryClientProvider> ) } ```

Mutation scopes example

Example showing how to use mutation scopes to run mutations serially: ```tsx const mutation = useMutation({ mutationFn: addTodo, scope: { id: 'todo', }, }) ```

Mutation scopes for serial execution

By default, all mutations run in parallel. Mutations can be given a scope with an id to ensure they run serially. All mutations with the same scope.id will run in serial order, starting in isPaused: true state if another mutation for that scope is already in progress. They queue and automatically resume when their turn comes.

Mutation invalidation and query client integration

Mutations become more powerful when combined with the Query Client's invalidateQueries method and setQueryData method, typically used in onSuccess callbacks to update cached query data.

useMutation hook purpose and usage

TanStack Query exports a useMutation hook for creating, updating, or deleting data and performing server side-effects, unlike queries which are for fetching data.

Mutation states and status values

A mutation can be in one of four states at any given moment: idle (isIdle or status === 'idle') - mutation is idle or in fresh/reset state; pending (isPending or status === 'pending') - mutation is running; error (isError or status === 'error') - mutation encountered an error; success (isSuccess or status === 'success') - mutation was successful and mutation data is available.

Mutation data and error properties

The error property is available when a mutation is in an error state. The data property is available when a mutation is in a success state.

Passing variables to mutations

Variables can be passed to a mutation's function by calling the mutate function with a single variable or object.

useMutation basic example

Example showing how to use useMutation with mutationFn to POST a new todo and handle pending, error, and success states: ```tsx function App() { const mutation = useMutation({ mutationFn: (newTodo) => { return axios.post('/todos', newTodo) }, }) return ( <div> {mutation.isPending ? ( 'Adding todo...' ) : ( <> {mutation.isError ? ( <div>An error occurred: {mutation.error.message}</div> ) : null} {mutation.isSuccess ? <div>Todo added!</div> : null} <button onClick={() => { mutation.mutate({ id: new Date(), title: 'Do Laundry' }) }} > Create Todo </button> </> )} </div> ) } ```

mutate function async behavior in React 16 and earlier

The mutate function is asynchronous, so it cannot be used directly in an event callback in React 16 and earlier due to React event pooling. If you need to access the event, wrap mutate in another function.

React event pooling workaround example

Example showing how to wrap mutate to handle form submission in React 16: ```tsx // This will not work in React 16 and earlier const CreateTodo = () => { const mutation = useMutation({ mutationFn: (event) => { event.preventDefault() return fetch('/api', new FormData(event.target)) }, }) return <form onSubmit={mutation.mutate}>...</form> } // This will work const CreateTodo = () => { const mutation = useMutation({ mutationFn: (formData) => { return fetch('/api', formData) }, }) const onSubmit = (event) => { event.preventDefault() mutation.mutate(new FormData(event.target)) } return <form onSubmit={onSubmit}>...</form> } ```

Resetting mutation state with reset function

The reset function can be called on a mutation to clear the error or data from a mutation request.

Access mutation variables in onSuccess callback

The onSuccess callback receives two arguments: the response data and the variables object that was passed to the mutate function. This allows you to construct the correct query key using the mutation variables.

Update query cache with mutation response using setQueryData

When a mutation returns the updated object, use the Query Client's setQueryData method in the onSuccess callback to update the existing query with the new data immediately, avoiding unnecessary refetches. Pass the query key and the response data to setQueryData.

setQueryData must use immutable updates

Updates via setQueryData must be performed in an immutable way. DO NOT attempt to mutate data in place by directly modifying the cache data. Instead, create a new object using spread operators or other immutable patterns. Mutating data in place may work initially but can lead to subtle bugs.

Example: Update query cache with mutation response

const queryClient = useQueryClient() const mutation = useMutation({ mutationFn: editTodo, onSuccess: (data) => { queryClient.setQueryData(['todo', { id: 5 }], data) }, }) mutation.mutate({ id: 5, name: 'Do the laundry', }) const { status, data, error } = useQuery({ queryKey: ['todo', { id: 5 }], queryFn: fetchTodoById, })

Example: Reusable mutation hook with setQueryData

const useMutateTodo = () => { const queryClient = useQueryClient() return useMutation({ mutationFn: editTodo, onSuccess: (data, variables) => { queryClient.setQueryData(['todo', { id: variables.id }], data) }, }) }

Example: Incorrect and correct immutable setQueryData patterns

// ❌ Incorrect: mutating data in place queryClient.setQueryData(['posts', { id }], (oldData) => { if (oldData) { oldData.title = 'my new post title' } return oldData }) // ✅ Correct: immutable update with spread operator queryClient.setQueryData( ['posts', { id }], (oldData) => oldData ? { ...oldData, title: 'my new post title', } : oldData, )

useMutation hook with onSuccess callback

The useMutation hook accepts an object with mutationFn and onSuccess properties. The mutationFn is the function to execute, and onSuccess is a callback invoked after the mutation succeeds. The hook returns an object with a mutate method to trigger the mutation.

mutationOptions accepts all useMutation options

The mutationOptions helper accepts all options that can be passed to the useMutation hook.

mutationOptions helper function signature

The mutationOptions helper function accepts mutationFn and any additional options that can be passed to useMutation. It is called with mutationOptions({ mutationFn, ...options }).

useIsMutating hook returns number of active mutations

useIsMutating is an optional hook that returns the number of mutations that the application is currently fetching. This is useful for displaying app-wide loading indicators.

useIsMutating with mutation key filter

useIsMutating accepts a mutationKey option to filter mutations by key. For example, useIsMutating({ mutationKey: ['posts'] }) returns only the count of mutations matching the posts prefix.

useIsMutating basic usage without filtering

Call useIsMutating() with no arguments to get the total number of mutations currently fetching across the entire application.

useIsMutating import statement

Import useIsMutating from '@tanstack/react-query'.

useIsMutating options and return type

useIsMutating accepts the following options: filters (MutationFilters, optional) and queryClient (QueryClient, optional). It returns isMutating as a number representing the count of mutations currently fetching. If queryClient is not provided, the hook uses the QueryClient from the nearest context.

useMutationState hook overview

useMutationState is a hook that gives access to all mutations in the MutationCache. It allows filtering mutations and transforming mutation state with a select function.

useMutationState filters parameter

The filters parameter accepts MutationFilters to narrow down which mutations are returned. Common filter options include status (e.g., 'pending') and mutationKey.

useMutationState select parameter

The select parameter is a function that receives a mutation object and returns a transformed value. The hook returns an array where each element is the result of applying select to each matching mutation.

useMutationState queryClient parameter

An optional queryClient parameter can be passed to use a custom QueryClient. If not provided, the QueryClient from the nearest context will be used.

Access latest mutation data from useMutationState

Each invocation of mutate adds a new entry to the mutation cache for gcTime milliseconds. To access the latest invocation from useMutationState results, use data[data.length - 1] where data is the array returned by useMutationState.

useMutationState example: access latest mutation

import { useMutation, useMutationState } from '@tanstack/react-query' const mutationKey = ['posts'] const mutation = useMutation({ mutationKey, mutationFn: (newPost) => { return axios.post('/posts', newPost) }, }) const data = useMutationState({ filters: { mutationKey }, select: (mutation) => mutation.state.data, }) const latest = data[data.length - 1]

Give your agent this brain