Invalidate or update every query a mutation changes
After a mutation succeeds, invalidate every cached query whose data it may have changed, or write the server's response into the cache when it contains the complete new value. Use the narrowest key prefix that still covers every affected query.
Implementation
- List what the mutation changes: the entity itself, lists and filtered views that contain it, counts and summaries, and related entities.
- Invalidate them in
onSuccess, or inonSettledwhen an optimistic update must be reconciled after failure too. - Return or await the
invalidateQueriespromise, so the mutation stays pending until the refetch finishes and the UI does not flash outdated data. - When the response contains the full updated entity, write it with
setQueryDatafor that entity's key, and invalidate lists and aggregates that may also have changed. - Prefer a prefix that covers what changed, such as
todoQueries.lists(). When unsure, invalidate a slightly broader prefix; an extra refetch of an active query costs less than outdated data. - Do not call
invalidateQueries()with no filter; it refetches every active query in the app.
Rationale
Invalidation marks matching queries stale and immediately refetches the active ones, the queries currently used by a mounted component. Inactive queries refetch the next time a component uses them. A query left out keeps its cached value, so the UI contradicts what the user just saved.
Examples
Incorrect (counterexample):
const deleteTodo = useMutation({
mutationFn: (todoId: number) => api.deleteTodo(todoId),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos', 'list'] });
},
});
The todo count shown in the header, cached under ['todos', 'count'], still includes the deleted todo.
Correct:
const deleteTodo = useMutation({
mutationFn: (todoId: number) => api.deleteTodo(todoId),
onSuccess: (_data, todoId) => {
queryClient.removeQueries({ queryKey: todoQueries.detail(todoId).queryKey });
return queryClient.invalidateQueries({ queryKey: todoQueries.all() });
},
});
todoQueries is the entity's query options factory.
The deleted todo's detail entry is removed, every list and count under ['todos'] refetches, and the mutation stays pending until they do.
Validation
For each mutation, list the screens that display data it changes, and check that each of their query keys is invalidated or updated. After running the mutation in the app, check that every such screen shows the new data without a manual reload.
Updating the cache directly from a complete server response instead of invalidating is not a violation.