Act on input, not on response
Update the UI on click, sync in the background, roll back on failure.
Guillermo Rauch7 Principles of Rich Web ApplicationsJavaScript allows us to mask network latency altogether. Applying this as a design principle should even remove most spinners or “loading” messages from your applications.
Guillermo Rauch7 Principles of Rich Web ApplicationsA click on a link or button can result in an immediate reaction without hitting the network. A famous example of this is Gmail (or Google Inbox), where archiving an email will happen immediately on the UI while the server request is sent and processed asynchronously.
Guillermo Rauch@rauchgSome engineering principles I live by: … ✓ Feedback must be given to users instantly
Guillermo Rauch@rauchgI first fell in love with React when I saw the incredible experiences they were shipping on FB Newsfeed. Realtime comments, notifications, chat, infinite scroll, optimistic likes… Always work backwards from the user experience when picking your tools.
Demo 1 rebuilds the Gmail archive from his essay; demo 3, the optimistic like.
1 · Archive now, sync later
Archive a few emails in each inbox.
Waits for the serverBad
Waited
0msActs on the clickGood
Waited
0msconst [emails, removeOptimistic] = useOptimistic(
serverEmails,
(list, id) => list.filter((e) => e.id !== id),
);
const archive = (id) =>
startTransition(async () => {
removeOptimistic(id); // the row leaves now
const res = await archiveEmail(id); // server action, runs behind
if (!res.ok) toast.error("Couldn't archive", { action: retry(id) });
}); // on failure serverEmails still has it: the row comes back
2 · Roll back when it fails
Keep “Server fails” on and archive an email in each pane.
No rollbackBad
Rolls back + retryGood
async function archive(email) {
const index = list.indexOf(email);
list.remove(email); // optimistic
try {
await api.archive(email.id);
toast("Archived", { action: { label: "Undo", onClick: () => list.insert(index, email) } });
} catch {
list.insert(index, email); // roll back to the truth
toast.error("Couldn't archive", { action: { label: "Retry", onClick: () => archive(email) } });
}
}
3 · The optimistic like
Like each post, then tap it again quickly.
Awaits the responseBad
Felt
0msOptimisticGood
Felt
0msWhy it matters, pros and cons
Why it matters
Almost every request succeeds. Show that success now and handle the rare failure when it comes.
Pros
- Zero perceived latency, on any network.
- No spinner per row; the list stays usable.
- Undo comes nearly free: it's the same rollback.
Cons
- Payments, logout: acting early “can result in a negative experience.”
- Needs a real rollback path and a visible error.
- Server-made values (ids, totals) still arrive later.