# Optimistic UI & Mutations > Update the UI before the server replies; roll back on error. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/optimistic-ui-mutations Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## Summary Optimistic UI applies a mutation to local state immediately, then confirms or rolls back once the server responds. The pattern eliminates the visible delay between user action and UI change. TanStack Query's `useMutation` lifecycle and React 19's `useOptimistic` are the two main implementation paths. ## Definition Optimistic UI updates the client as if a mutation already succeeded, then rolls back if the server errors. The payoff is instant perceived response: a like flips immediately, a todo appears the moment you press Enter. Reverting on error is rare enough that users accept it. Two concerns compound the pattern: - **Idempotency**: if a retry sends the request twice, the server must produce the same result. Use idempotency keys or PUT/PATCH, not bare POST. - **Race conditions**: a refetch can resolve after your optimistic write and overwrite it. Cancel in-flight queries in `onMutate` before writing. ## Diagram ```mermaid sequenceDiagram participant User participant UI participant Cache participant Server User->>UI: click / submit UI->>Cache: onMutate → snapshot + setQueryData (optimistic) UI-->>User: instant update shown UI->>Server: mutation request alt success Server-->>UI: 200 OK UI->>Cache: onSettled → invalidateQueries Cache-->>UI: fresh server data else error Server-->>UI: 4xx / 5xx UI->>Cache: onError → setQueryData(snapshot) UI-->>User: reverted + error shown end ``` Optimistic update applies immediately to the cache; rollback restores the snapshot on error, and invalidation syncs on success. ## TanStack Query v5 — snapshot, optimistic write, rollback Three callbacks form the full lifecycle: cancel in-flight queries in `onMutate`, snapshot and write optimistically, then roll back in `onError` and sync in `onSettled`. ```tsx import { useMutation, useQueryClient } from "@tanstack/react-query"; type Todo = { id: string; text: string; done: boolean }; function useTodoToggle() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => fetch(`/api/todos/${id}/toggle`, { method: "PATCH" }).then((r) => r.json() ), onMutate: async (id) => { // 1. Cancel outgoing refetches — prevents stale data overwriting optimistic value await queryClient.cancelQueries({ queryKey: ["todos"] }); // 2. Snapshot current cache const previous = queryClient.getQueryData(["todos"]); // 3. Write optimistic update queryClient.setQueryData(["todos"], (old = []) => old.map((t) => (t.id === id ? { ...t, done: !t.done } : t)) ); return { previous }; // context passed to onError / onSettled }, onError: (_err, _id, context) => { // Restore snapshot on failure if (context?.previous) { queryClient.setQueryData(["todos"], context.previous); } }, onSettled: () => { // Sync cache with server truth regardless of outcome queryClient.invalidateQueries({ queryKey: ["todos"] }); }, }); } ``` `cancelQueries` stops a race where an in-flight refetch overwrites the optimistic value. The snapshot returned from `onMutate` lets `onError` restore previous state. ## Note: React 19 useOptimistic needs no manual rollback Call `useOptimistic(serverValue, reducer)` to get `[optimisticState, setOptimistic]`. Inside `startTransition`, call `setOptimistic(nextValue)` before the `await`. React displays the optimistic value until the transition settles or throws, then reverts automatically — no `setQueryData` needed. ## Key terms - **onMutate**: TanStack Query callback that runs before the fetch; returns a context snapshot for rollback. - **onError**: Mutation callback receiving the `onMutate` context; restores the cache snapshot on failure. - **onSettled**: Fires after success or error; the right place to call `invalidateQueries`. - **useOptimistic**: React 19 hook that applies an optimistic reducer and auto-reverts when the transition settles. - **idempotency**: Property where repeating a request produces the same result; required for safe retries. ## Related topics - [Server State & Data Fetching](https://fearchitect.com/topics/server-state-data-fetching.md): Async, shared, remote-owned data that requires a dedicated cache layer. - [Client State Management](https://fearchitect.com/topics/client-state-management.md): Decide where UI state lives; pick the right tool for the scope. - [Real-time: WebSockets vs SSE vs Polling](https://fearchitect.com/topics/realtime-websockets-sse-polling.md): Match the right real-time transport to your data-flow direction. - [Signals & Fine-Grained Reactivity](https://fearchitect.com/topics/signals-fine-grained-reactivity.md): Observable values that re-run only their exact dependents. - [State Machines](https://fearchitect.com/topics/state-machines.md): Model UI as explicit states with typed transitions to kill impossible states. - [REST vs GraphQL vs tRPC](https://fearchitect.com/topics/rest-vs-graphql-vs-trpc.md): Three API styles with distinct fetch, type, and caching trade-offs. ## Further reading - [TanStack Query — Optimistic Updates](https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates) - [React docs — useOptimistic](https://react.dev/reference/react/useOptimistic) - [React docs — useTransition](https://react.dev/reference/react/useTransition) - [TanStack Query — useMutation](https://tanstack.com/query/latest/docs/framework/react/reference/useMutation)