React: From Components to Production · Fetching and Caching Server Data
Mutations, Invalidation and Optimistic Updates
Change server data and keep the cache consistent.
Use useMutation for creates/updates/deletes. On success, invalidate affected queries so they refetch, or update the cache directly with setQueryData. For snappy UIs, apply an optimistic update in onMutate, roll back in onError, and refetch in onSettled.
const queryClient = useQueryClient();
const toggleTodo = useMutation({
mutationFn: (todo: Todo) =>
fetch(`/api/todos/${todo.id}`, { method: "PATCH", body: JSON.stringify({ done: !todo.done }) }).then((r) => {
if (!r.ok) throw new Error("Failed");
return r.json();
}),
onMutate: async (todo) => {
await queryClient.cancelQueries({ queryKey: ["todos"] });
const previous = queryClient.getQueryData<Todo[]>(["todos"]);
queryClient.setQueryData<Todo[]>(["todos"], (old = []) =>
old.map((t) => (t.id === todo.id ? { ...t, done: !t.done } : t)),
);
return { previous };
},
onError: (_err, _todo, ctx) => queryClient.setQueryData(["todos"], ctx?.previous),
onSettled: () => queryClient.invalidateQueries({ queryKey: ["todos"] }),
});
<input type="checkbox" checked={todo.done} onChange={() => toggleTodo.mutate(todo)} />
Common pitfalls
- Forgetting to invalidate related queries after a mutation.
- Optimistic updates without rollback on error.
- Not cancelling in-flight queries that could overwrite the optimistic state.
Key points
- useMutation for writes
- Invalidate or update the cache
- Optimistic updates need rollback
Further reading: TanStack Query: optimistic updates
Practice questions
Why call cancelQueries in onMutate?
Answer
An in-flight refetch could resolve after the optimistic update and overwrite it with stale server data.
When would you use setQueryData instead of invalidation?
Answer
When the mutation response contains the updated resource, you can write it directly into the cache and skip a refetch.
Open in the app to mark lessons complete, take notes and track your progress.