Core Patterns
Use local mutation callbacks for one-off behavior. Use MutationCache callbacks when the app wants a consistent invalidation policy for every mutation.
Global invalidation after successful mutations
import { MutationCache, QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: (_data, _variables, _context, mutation) => {
return queryClient.invalidateQueries({
queryKey: mutation.options.mutationKey,
})
},
}),
})
If a mutation has mutationKey: ['issues'], this invalidates matching issue queries. If it has no mutation key, this becomes a broad invalidation policy, so only use that deliberately.
Use meta for explicit invalidation tags
import { matchQuery, MutationCache, QueryClient } from '@tanstack/react-query'
const queryClient = new QueryClient({
mutationCache: new MutationCache({
onSuccess: (_data, _variables, _context, mutation) => {
return queryClient.invalidateQueries({
predicate: (query) =>
mutation.meta?.invalidates?.some((queryKey) =>
matchQuery({ queryKey }, query),
) ?? true,
})
},
}),
})
Common Mistakes
HIGH Invalidating the whole app for every mutation
Wrong:
new MutationCache({
onSuccess: () => queryClient.invalidateQueries(),
})
Correct:
new MutationCache({
onSuccess: (_data, _variables, _context, mutation) =>
queryClient.invalidateQueries({ queryKey: mutation.options.mutationKey }),
})
Global policies need scope. Reach for mutation keys or meta tags before invalidating everything.
Source: https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations
HIGH Not returning invalidation when pending UI depends on refetch
Wrong:
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
}
Correct:
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] })
Returning the promise keeps the mutation pending until the invalidation refetch completes.
Source: TanStack/query:docs/framework/react/guides/invalidations-from-mutations.md
MEDIUM Refetching data that should be static
Wrong:
useQuery({
queryKey: ['build-info'],
queryFn: fetchBuildInfo,
staleTime: Infinity,
})
Correct:
useQuery({
queryKey: ['build-info'],
queryFn: fetchBuildInfo,
staleTime: 'static',
})
If a query must not refetch even after broad manual invalidation, mark it with staleTime: 'static'.
Source: https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations