Topic 19.6
useOptimistic
In one line
useOptimistic(state, update?) shows a temporary, hoped-for version of some state while an Action runs; when the Action ends React drops the temporary value and shows the real state again, so a failed request reverts by itself.
Think of it like this
Writing a guest's name on a party list in pencil while you wait for their RSVP card. Everyone can already see the name on the list. When the card comes back "yes", you write the name in pen; if it comes back "no", the pencil mark is rubbed out. Optimistic UI is the pencil: show the result you expect right away, then make it permanent or erase it when the real answer arrives.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Optimistic UI
- Showing the expected result immediately, before the server confirms it.
- Pessimistic UI
- Waiting for the server's answer before showing the result, with a loading state meanwhile.
- useOptimistic
- A hook that shows a temporary value during an Action and drops it when the Action ends.
- Passthrough state
- The real state you give
useOptimistic. It is shown whenever no Action is running. - Revert
- Going back to the previous value, here automatically when the Action ends without updating the real state.
- Rebase
- Re-applying pending optimistic changes on top of newer real state.
Step by step
01Start from real state
Keep the real, confirmed data in state as usual. Then pass it to useOptimistic with a function that says how an optimistic change is applied.
const [likes, setLikes] = useState(10);
const [shownLikes, addOptimistic] = useOptimistic(
likes,
(current, delta) => current + delta,
);02Apply the optimistic change inside an Action
In the Action, call addOptimistic first, then talk to the server. The screen updates at once.
startTransition(async () => {
addOptimistic(1); // shown now
await api.like(); // may fail
startTransition(() => setLikes((l) => l + 1)); // real state
});03Success and failure
On success the real state catches up with the optimistic one, and when the Action ends React shows the real state: same number, no flicker. On failure the real state never changed, so dropping the optimistic value is the revert.
04Mark pending items
For lists, add a flag such as sending: true in the update function, and style those items differently. Users then know which items are confirmed.
Try it yourself
- 1
Slow it down
In the likes example, change the delay to 1500 ms and click "Like (works)" three times quickly. Predict what the number shows during and after. Each pending Action adds 1 on top of the real value.
- 2
Keep the failed text
In the chat, the form resets even when sending fails, because the action itself didn't throw. Put the failed text back: store it in state and give the input
defaultValue={failedText}pluskey={failedText}. Predict whether the box shows it after a failure. - 3
No update function
Rewrite the likes hook as
useOptimistic(likes)and calladdOptimistic((n) => n + 1). Does it behave the same?
Code & diagrams
The first like shows 11 at once and stays (the real state caught up). The second shows 12, the server fails, and when the Action ends React drops the optimistic value: back to 11 with no undo code.
Preview
Console
Nothing logged yet.Expected console output
render likes=10 shown=10
render likes=10 shown=11
render likes=11 shown=11
render likes=11 shown=12
failed, reverting
render likes=11 shown=11Each message appears instantly, dimmed. `Hello` is confirmed; `please fail` disappears when its Action ends and the error explains why. Try sending your own messages, including one with the word fail.
Preview
Console
Nothing logged yet.Expected console output
render: Welcome!
render: Welcome!, Hello (sending)
render: Welcome!, Hello
render: Welcome!, Hello, please fail (sending)
render: Welcome!, HelloBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Call addOptimistic outside an Action
Call addOptimistic(1) directly in a button's onClick, without startTransition.
Break #2
Set the real state with an urgent update
In the chat, replace startTransition(() => setMessages((m) => [...m, saved])) with setMessages((m) => [...m, saved]).
Myth vs fact
Myth
Optimistic updates need manual rollback code.
Fact
With useOptimistic the rollback is automatic: the optimistic value disappears when the Action ends. You only update real state on success and show an error on failure.
Myth
useOptimistic changes the real state.
Fact
It never touches your state. It shows a temporary value derived from it; you still set the real state yourself.
Myth
Everything should be optimistic.
Fact
Use it for cheap, usually-successful, easily-reversible actions. Payments and destructive actions should wait for confirmation.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Optimistic updates are stored like pending updates on a hook queue and re-applied on top of the latest passthrough value on every render until their transition finishes, which is how they survive other state changes (rebasing).
- ▸
Because the optimistic value is tied to the transition, a queue of
useActionStatedispatches keeps every optimistic item visible until the whole queue drains. - ▸
Use stable keys for optimistic list items (a client-generated id, not the array index) so React keeps their DOM nodes when the confirmed item replaces the optimistic one.
crypto.randomUUID()in the action is fine; the server can return the same id. - ▸
Announce failures accessibly: a
role="alert"message when an optimistic item is reverted, since the item silently disappearing can confuse screen-reader and sighted users alike.
Remember this
- 1
Optimistic UI means updating the screen before the server confirms, because most requests succeed. Likes, chat messages, ticking a to-do: waiting 300 ms for each feels sluggish. The opposite, pessimistic UI, waits for the answer and shows a spinner. Optimistic is faster to feel, but you must handle failure honestly.
- 2
The hook.
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn).stateis the real value (usually fromuseState, props oruseActionState).optimisticStateequalsstatewhenever no Action is running. CallingaddOptimistic(value)during an Action makes React computeupdateFn(currentState, value)and show that instead. WithoutupdateFn,addOptimistic(newValue)sets the optimistic value directly (and accepts an updater function likesetState). - 3
When the Action ends, the optimistic value is thrown away. React goes back to showing
state. If the Action succeeded, it should have updatedstate(for examplesetMessages([...m, saved])), so the screen shows the confirmed version and nothing seems to change. If it failed and didn't updatestate, the screen goes back to the old value: the revert happens automatically. You don't write undo code; you only show an error message. - 4
It only works inside an Action. Call
addOptimisticinsidestartTransition, a form action, or an action fromuseActionState. Outside one React warnsAn optimistic state update occurred outside a transition or action.and the value is dropped at once. While the Action runs, React also re-applies the optimistic updates on top of any newer real state (it rebases), so other updates don't wipe the pending item. - 5
Combining with actions. In a form action: read the FormData, call
addOptimistic,awaitthe server, then update the real state insidestartTransition(Topic 19.2). If you set the real state with a plain urgent update after theawait, it commits before the Action ends while the optimistic item is still applied, and the user sees the item twice for a moment. Mark optimistic items (sending: true) so the UI can show them dimmed or with "Sending...": being honest about what isn't saved yet matters for trust and accessibility. - 6
When not to be optimistic: payments, deleting important data, anything where a reverted result would confuse or harm the user, or requests that fail often. There, show a pending state (Topic 19.5) and wait.
Explain it without notes
What is optimistic UI, and when is it a good idea?
How does useOptimistic revert a failed update?
Why must addOptimistic be called inside an Action?
How do you combine useOptimistic with a form action correctly?
Practice
Make a to-do checkbox that toggles optimistically and reverts if fakeToggle() rejects.
Add optimistic "(sending)" items to a comment form backed by useActionState.
Trade-offs
- ↔
Optimistic UI feels instant, but users may act on data that later disappears; mark pending items and keep failures visible.
- ↔
Reverting is automatic only for the optimistic value. Side effects you started (navigation, analytics, other state) are not undone for you.
Done when you can
I can explain optimistic vs pessimistic UI.
I can show an optimistic value with
useOptimisticinside an Action.I rely on the automatic revert and show an error on failure.
I update real state in
startTransitionafterawaitto avoid duplicates.I know when not to use optimistic updates.