Optimistic UI shows the result of a change before the server has confirmed it. That sounds like a presentation choice, but it isn’t. The moment the interface displays a prediction, your application has two versions of the truth: what the user sees and what the server has accepted. You then have to define how long they may differ, what happens when they never converge, and who cleans up.
This article walks through that lifecycle, compares three implementation layers (component state, query cache, transactional store), and ends with a decision rule for when optimism is the wrong tool. It is based on the official documentation for React and TanStack. Those sources publish no latency or adoption figures, so none are quoted here.
As an Amazon Associate I earn from qualifying purchases.
A concrete case: toggling a setting
A user flips “Email notifications” off. There are three ways to respond:
Free tools Windows power users keep installed
One-click scans. No signup required.
- Wait for confirmation: the switch stays on, perhaps disabled with a spinner, until the server replies.
- Pending-only feedback: the switch stays put and shows a “saving” indicator, but the state does not change yet.
- Optimistic update: the switch flips immediately, and the app reconciles when the response arrives.
The third option feels instant only when the request succeeds. If it fails, the app must flip the switch back and tell the user. If a background refetch lands in the meantime with the old value, the switch may flicker or revert silently. Each of those behaviours is a design decision about state, not styling.
#1 Best Overall
Why this is an architecture question
Server-owned data has one canonical source. An optimistic update adds a temporary layer over it. Once that layer exists, you must answer questions that a wait-for-confirmation design never asks:
- Where does the temporary value live, and who is allowed to read it?
- What marks it as unconfirmed?
- What happens on rejection, and does it undo only this change or also later edits built on top of it?
- What if another write, another user, or a refresh changes the same record meanwhile?
TanStack’s documentation states the core risk plainly: “When you optimistically update your state before performing a mutation, there is a chance that the mutation will fail.” (TanStack Query, Optimistic Updates). Everything below follows from taking that sentence seriously.
The optimistic mutation lifecycle
- Pending intent. The user’s action is recorded as something requested, not something done.
- Optimistic projection. The UI renders the predicted result. This is a display of expectation. It does not mean the data was saved.
- Server outcome. The request succeeds, is rejected, or fails in transit.
- Reconciliation. On success, the authoritative value replaces the projection, which may differ from the prediction (server-generated IDs, timestamps, normalised fields). On failure, the projection is rolled back or the state is refetched.
A design that implements steps 1 and 2 but leaves 3 and 4 to chance is a bug generator, not an optimistic UI.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallThree implementation layers
Component-level temporary state: React useOptimistic
React’s useOptimistic returns an optimistic value and a setter (or reducer dispatch) that you use inside an Action. The temporary state exists while the Action is in progress; the canonical value stays separate, held in props or state. Once the Action finishes, the UI falls back to the real value. That makes the temporary layer explicit at the component and action boundary, and keeps its scope small.
Concurrency is addressed through the reducer form. If the base value changes while a Transition is pending, React can rerun the reducer against the updated list, so pending intent is reapplied on top of the new base. In React’s words, this ensures the UI stays consistent (React useOptimistic reference). This is rebasing: the pending change is replayed over fresh data.
Server-state cache mutation: TanStack Query
Here the optimistic value is written into the shared cache, so every component reading that data sees it. The official guide demonstrates this sequence (TanStack Query v4 guide):
- Cancel outgoing refetches so they cannot overwrite the optimistic change.
- Snapshot the previous cached value.
- Write the expected value into the cache.
- On error, restore the snapshot.
- After the mutation settles, invalidate the query so it refetches authoritative state.
This couples the UX to the cache and mutation lifecycle. Cancellation protects against stale reads, the snapshot defines the rollback, and invalidation is the reconciliation step. The wider reach is both the benefit (consistent display everywhere) and the cost (a bad rollback affects every consumer).
Transaction-oriented state: TanStack DB
TanStack DB applies local optimistic state through transactions with handler-defined settlement. Its guide draws a distinction many teams miss: a transaction completing locally is not proof the backend persisted anything. In its words, “Completion proves backend confirmation only when the handler explicitly waited for that confirmation or read-back.” (TanStack DB mutations).
Rank #3
So if your product must tell users that something is saved, your handler has to wait for sync-back or read-back before settling. Otherwise “done” in your code means only “done locally.”
Comparison
| Layer | Where the temporary state lives | Recovery mechanism | Main thing to design |
|---|---|---|---|
React useOptimistic |
Component, scoped to an Action | Reverts to the canonical value when the Action ends; reducer replays pending intent if the base changes | Keeping the canonical value correct and showing error feedback |
| TanStack Query cache | Shared query cache | Restore snapshot on error, invalidate and refetch on settle | Cancelling refetches, snapshot correctness, scope of cache keys |
| TanStack DB transactions | Local transactional layer | Handler-defined settlement and sync-back | What “settled” means; whether the handler waits for confirmation |
Failure and concurrency
Rollback is not always a simple undo
Restoring a snapshot is easy when one change is in flight. With several, a snapshot taken before change A may erase a valid change B made afterwards. Before choosing snapshot restoration, check whether you can reverse the failed change without discarding later valid edits. If you can’t, refetching authoritative state is safer than restoring old data.
Parallel and serialized writes
TanStack Query mutations run in parallel by default. Mutations that share a scope.id run serially (TanStack Query mutations). Serial execution fixes ordering of your own writes: edit 2 is not sent until edit 1 finishes. It does not decide what happens if another user, a background refresh, or an earlier pending write changes the underlying record. Serialization and rebasing are separate tools, and neither is a conflict-resolution policy. You still need to decide who wins, whether the user is told, and whether the record is refetched.
Recommended Free Tools
Visible success versus real success
Distinguish the states your product actually needs: submitted, accepted, persisted, synchronized. If users or downstream systems rely on “persisted”, a green check shown at the optimistic stage is a false claim. Display optimistic items in a visibly pending style when the difference matters, and make failures impossible to miss.
Rank #4
When not to be optimistic
TanStack DB’s guidance suggests considering non-optimistic behaviour for complex server-side processing, validation requirements, confirmation workflows, and disruptive batch operations (TanStack DB mutations). Reasons are practical: the client can’t predict the result, the server may legitimately reject it, or reversing a displayed change would confuse the user.
The following axes are an editorial synthesis, not a vendor rubric. Use them to compare optimistic, pending-only, and wait-for-confirmation designs for a given mutation:
| Axis | Question | Leans optimistic when… |
|---|---|---|
| Predictability | Can the client compute the result without server-generated fields or complex rules? | Yes, trivially |
| Reversibility | Can a rejection be undone without erasing later valid edits? | Yes, cleanly |
| Confirmation meaning | Must users distinguish submitted, accepted, persisted, synchronized? | No, or the pending state is clearly shown |
| Concurrency | Can other writes or users touch the record while pending? | Rarely, or a conflict policy exists |
| User impact | Would showing a deletion, payment, or permission change as complete mislead? | No |
| Reconciliation ownership | Which layer owns cache updates, rollback, invalidation, error messages? | One layer clearly does |
Likely good fits: toggles, likes, reordering, renaming, and checking off a task. Likely poor fits: payments, permission changes, destructive deletions with side effects, and anything gated by server validation.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
A decision rule
Use immediate optimistic updates when the result is predictable and recoverable. Use pending-only feedback when the outcome is probably fine but you need honesty about its status. Wait for confirmation when server rules, validation, or user consequences would make a temporary “success” misleading. Whichever you pick, write down the pending state, the rejection path, the reconciliation owner, and the concurrency policy before building it.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




