Optimistic Updates
Purpose and Scope
Optimistic updates let an application show the expected result of a mutation before the server confirms it. In TanStack Query, this is specifically about mutation-driven UI: a user creates or edits data, the interface responds immediately, and the query cache or rendered mutation state is reconciled after the mutation succeeds or fails. The React guide presents two supported strategies: render the mutation variables in the UI without touching cached query data, or use onMutate to update cached data directly before the mutation function finishes.
Sources: docs/framework/react/guides/optimistic-updates.md
The practical choice is about where the temporary state should live. UI-only optimistic updates are simpler because they do not alter the cache. They work well when the component that owns the mutation is near the component that renders the affected query. Cache-based optimistic updates are more powerful because every observer of that query key sees the optimistic value, but they require cancellation, snapshotting, rollback, and invalidation discipline. The React and Angular guides use the same conceptual model while adapting names to framework APIs.
Sources: docs/framework/react/guides/optimistic-updates.md, docs/framework/angular/guides/optimistic-updates.md
Relevant Source Files
- docs/framework/react/guides/optimistic-updates.md - Primary React guide describing UI-based optimistic rendering, cache-based updates with
onMutate, rollback, invalidation, and concurrent pending mutations throughuseMutationState. - docs/framework/angular/guides/optimistic-updates.md - Angular adaptation of the same guide, mapping React APIs such as
useMutationanduseMutationStatetoinjectMutationandinjectMutationState. - examples/react/optimistic-updates-cache/src/pages/index.tsx - Runnable React cache-update example using
QueryClientProvider,queryOptions,useQuery,useMutation,onMutate,cancelQueries,getQueryData,setQueryData, andinvalidateQueries. - examples/react/optimistic-updates-ui/src/pages/index.tsx - Runnable React UI-only example that appends
addTodoMutation.variableswhile pending and exposes retry behavior after an error. - examples/angular/optimistic-updates/src/main.ts - Angular example bootstrap entry point that starts
AppComponentwithappConfigfor the optimistic updates example application.
Two Strategies
The UI strategy uses the mutation result as temporary render input. A React mutation posts the new todo and returns the Promise from queryClient.invalidateQueries({ queryKey: ['todos'] }) in onSettled, keeping the mutation pending until the refetch finishes. While isPending is true, the rendered list appends variables with reduced opacity. If the mutation succeeds, the refetched query data replaces the temporary item. If it fails, variables remain available, so the UI can render an error row and retry by calling mutate(variables).
Sources: docs/framework/react/guides/optimistic-updates.md, examples/react/optimistic-updates-ui/src/pages/index.tsx
This UI-only approach is intentionally local. The React example defines a useTodos query for ['todos'], submits text through addTodoMutation.mutate(text), clears the input, disables the Create button while isPending, and renders normal todo rows from todoQuery.data.items. The temporary item is not written into the query cache; it is rendered from mutation state. That makes the approach easy to reason about because failure automatically removes the pending row unless the component explicitly renders the error state.
Sources: examples/react/optimistic-updates-ui/src/pages/index.tsx
The cache strategy moves the temporary state into the cache. In the React cache example, todoListOptions centralizes the ['todos'] key and fetchTodos query function through queryOptions. The mutation's onMutate first clears the input, cancels outgoing refetches for the todo list, snapshots the previous cached value, and writes a new item into previousTodos.items with a generated id. Because the cache changes, any active observer of that query key can display the optimistic list immediately.
Sources: examples/react/optimistic-updates-cache/src/pages/index.tsx
Cache Rollback Flow
A safe cache optimistic update has four phases. First, cancel outgoing refetches so an older response does not overwrite the optimistic value. Second, snapshot the previous cache state with getQueryData. Third, write the expected value with setQueryData. Fourth, return a rollback payload from onMutate; the guides commonly return an object such as { previousTodos }. That returned value is passed into later mutation callbacks, giving onError enough information to restore the cache if the server rejects the mutation.
Sources: docs/framework/react/guides/optimistic-updates.md, examples/react/optimistic-updates-cache/src/pages/index.tsx
The React cache example demonstrates the failure path directly. onError checks whether onMutateResult?.previousTodos exists and restores ['todos'] with that saved value by calling context.client.setQueryData<Todos>(['todos'], onMutateResult.previousTodos). onSettled then invalidates ['todos'] after either error or success. This final invalidation is important because optimistic data is only a client-side prediction. The authoritative list still comes from the server after the mutation lifecycle completes.
Sources: examples/react/optimistic-updates-cache/src/pages/index.tsx
Angular follows the same lifecycle with framework-native entry points. The Angular guide uses injectMutation and receives the QueryClient through the mutation callback context. Its list example cancels ['todos'], snapshots the previous value, writes the new todo into cached list data, returns { previousTodos }, restores that snapshot in onError, and invalidates the query in onSettled. For item updates, it uses a more specific key, ['todos', newTodo.id], so rollback targets the same entity-level cache entry that was optimistically changed.
Sources: docs/framework/angular/guides/optimistic-updates.md
Concurrent Optimistic Updates
When the mutation and query do not live in the same component, React Query exposes pending mutations through useMutationState. The guide recommends combining it with a mutationKey, for example ['addTodo'], and filtering for { mutationKey: ['addTodo'], status: 'pending' }. The selected value can be mutation.state.variables, giving another component access to the submitted todos. Because there may be multiple pending mutations at once, the result is an array rather than a single value.
Sources: docs/framework/react/guides/optimistic-updates.md
Concurrent rendering needs stable keys for temporary items. The React guide notes that mutation.state.submittedAt can be selected alongside variables to create unique render keys for multiple pending mutations. This matters when a user submits several todos before the first request settles: each optimistic row should remain distinguishable, retryable, and visually consistent. Angular exposes the same idea through injectMutationState, using the same mutationKey and pending-status filter but returning signal-style state for Angular templates and components.
Sources: docs/framework/react/guides/optimistic-updates.md, docs/framework/angular/guides/optimistic-updates.md
API Components Reference
| Component | Where it appears | Role in optimistic updates |
|---|---|---|
useMutation | React guide and examples | Defines mutationFn, onMutate, onError, onSettled, mutationKey, and exposes isPending, isError, variables, and mutate. |
injectMutation | Angular guide | Angular function equivalent for defining mutation behavior and lifecycle callbacks. |
useMutationState | React guide | Reads matching mutations from elsewhere in the app, commonly filtered by mutationKey and status. |
injectMutationState | Angular guide | Angular equivalent for selecting mutation variables from pending mutations. |
context.client.cancelQueries | React cache example and Angular guide | Prevents in-flight refetches from overwriting optimistic cache writes. |
context.client.getQueryData | React cache example and Angular guide | Captures a rollback snapshot before changing cached data. |
context.client.setQueryData | React cache example and Angular guide | Writes optimistic data or restores a previous snapshot. |
invalidateQueries | Guides and examples | Refetches authoritative data after success or failure. |
A minimal cache-update mutation has this shape:
useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo, context) => {
await context.client.cancelQueries({ queryKey: ['todos'] })
const previousTodos = context.client.getQueryData(['todos'])
context.client.setQueryData(['todos'], (old) => [...old, newTodo])
return { previousTodos }
},
onError: (err, newTodo, result, context) => {
context.client.setQueryData(['todos'], result.previousTodos)
},
onSettled: (data, error, variables, result, context) => {
context.client.invalidateQueries({ queryKey: ['todos'] })
},
})Sources: docs/framework/react/guides/optimistic-updates.md, docs/framework/angular/guides/optimistic-updates.md
Implementation Notes and Next Steps
Use UI-based optimistic updates when the temporary value is only needed in the current rendering path, especially for append-style interactions where variables are enough to represent the pending item. Use cache-based optimistic updates when other components, routes, or observers should immediately see the predicted state. In both cases, return the invalidation Promise from settlement callbacks when the pending state should last until refetch completion, and keep query keys aligned between cancellation, snapshot, writes, rollback, and invalidation.
Sources: docs/framework/react/guides/optimistic-updates.md, examples/react/optimistic-updates-ui/src/pages/index.tsx, examples/react/optimistic-updates-cache/src/pages/index.tsx
To go deeper, read the mutation lifecycle and invalidation pages before applying optimistic updates broadly. Optimistic UI depends on mutation state, stable query keys, and predictable cache writes. For framework transfer, compare the React examples with the Angular guide: the names change from hooks to injection functions, but the operational sequence remains cancel, snapshot, write, rollback, and invalidate. The Angular example entry point confirms the example is a normal bootstrapped Angular app, while the guide provides the framework-specific optimistic update code.
Sources: docs/framework/angular/guides/optimistic-updates.md, examples/angular/optimistic-updates/src/main.ts