Dev.to WebDev 🛠 Dev 👁 1 📖 2 min read

I Built a useOptimistic Lab: Instant UI With Automatic Rollback (React 19)

"Optimistic UI" — update the screen before the server responds so the app feels instant — used to mean hand-rolling a temporary state and a manual rollback on error. React 19's useOptimistic bakes it into the framework.

"Optimistic UI" — update the screen before the server responds so the app feels instant — used to mean hand-rolling a temporary state and a manual rollback on error. React 19's useOptimistic bakes it into the framework. So I built a tiny chat where you can watch the instant update and the automatic revert.

▶ Live demo: https://useoptimistic-lab.vercel.app/
Source (React 19): https://github.com/dev48v/useoptimistic-lab

Send a message: the bubble appears immediately as sending, then commits to ✓ sent when the fake server confirms. Flip "next send fails" and watch the bubble simply vanish — no rollback code.

The hook

const [optimistic, addOptimistic] = useOptimistic(
  messages,                                              // real, committed state
  (cur, text) => [...cur, { text, status: "sending" }]   // how to show a pending change
);

startTransition(async () => {
  addOptimistic(text);              // 1. render instantly from optimistic state
  const saved = await save(text);   // 2. the real request
  setMessages(m => [...m, saved]);  // 3. commit → optimistic reconciles into real
  // on throw: no commit → React discards the optimistic value and snaps back
});

Three things happen:

  • Instant — addOptimistic derives a temporary state that renders right away; the user never waits on the network.
  • Reconcile — when the action commits the real state (setMessages), the optimistic entry is replaced by the confirmed one ("sending…" → "✓ sent").
  • Auto-rollback — if the async action finishes without committing (it threw), React throws the optimistic value away and the UI returns to the real state. The failed bubble disappears on its own.

The mental model that makes it click

The revert isn't something you code — it's driven by whether the underlying state actually changed by the time the action (transition) ends. Commit → the optimistic value merges in. Don't commit → it's discarded. That's why there's no catch block restoring old state anywhere in the example.

One gotcha: useOptimistic has to be updated inside a transition or a form action, so React knows when the optimistic window opens and closes. The demo uses startTransition with an async function; with <form action> + useActionState it's even shorter.

If this made optimistic UI click, a star helps others find it: https://github.com/dev48v/useoptimistic-lab

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.