Optimistic UI in React Without the Common Bugs
Optimistic UI renders the outcome a user is likely to get before the server has finished the mutation. It can make likes, status changes, comments, and small edits feel immediate. The difficult part is keeping that temporary projection separate from authoritative server data.
React's useOptimistic is useful for that temporary projection. The server should still remain the source of truth.
Choose optimistic operations deliberately
Good candidates have a predictable outcome, a low failure rate, and a recovery path the user can understand. A financial transfer, complex checkout, or operation whose result depends heavily on server-side policy usually deserves a more conservative loading state.
Project from real state instead of cloning it
import { startTransition, useOptimistic } from "react"
type Comment = { id: string; body: string; pending?: boolean }
function Comments({ comments }: { comments: Comment[] }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
(current, draft: Comment) => [...current, draft],
)
async function submit(body: string) {
const draft = { id: crypto.randomUUID(), body, pending: true }
startTransition(async () => {
addOptimisticComment(draft)
await createComment({ body })
})
}
return optimisticComments.map((comment) => (
<article key={comment.id} aria-busy={comment.pending}>{comment.body}</article>
))
}
The temporary item exists to make the interaction responsive. Once authoritative data arrives, that data should drive the rendered result.
Treat temporary identity as temporary
If the server assigns IDs, do not pretend a client-generated ID is final. A common duplication bug happens when an optimistic item is kept locally and the successful server response is appended as another item.
Design failure before shipping success
Recovery may mean restoring the previous value, keeping an editable draft, or offering retry. Destructive and multi-step operations need more care than a reversible toggle.
Decide what concurrent actions mean
Responses do not have to arrive in click order. Define whether actions are independent, whether the latest intent wins, whether the server exposes an authoritative version, or whether duplicate actions should be temporarily prevented.
Transitions do not replace mutation policy
A Transition helps React schedule non-urgent work. It does not decide retry rules, validate server responses, or define rollback behavior.
User intent
↓
Optimistic projection
↓
Server mutation
↓
Success → authoritative state
Failure → feedback / recovery
Avoid two optimistic owners
If TanStack Query or another server-state library owns the resource, avoid building a second independent cache protocol around the same data. Use one clear owner for mutation lifecycle and cache reconciliation.
Production checklist
Verify that server data remains authoritative, pending UI is distinguishable, failures have a recovery path, successful responses cannot create duplicates, repeated actions have defined semantics, and the feature does not maintain an unnecessary permanent mirror of server state.
Optimistic UI works best when it hides latency without hiding uncertainty. If reconciliation becomes more complicated than the interaction itself, reconsider whether the operation should be optimistic at all.