Optimistic UI

UI updates before the server confirms — the click feels instant, and a slow network only matters if the request fails. On error we roll back to the pre-click value. The API here returns 500 on ~14% of calls so you can see the rollback path: click enough times and an optimistic +1 disappears.

Tip
Always capture the pre-click value before mutating state — it's your only rollback snapshot. On success, reconcile to the server's authoritative value (not just +1) to avoid drift when other clients mutate the same resource.

Try it


40
in sync
src/components/OptimisticLikes.tsxTSX
// 1. Bump optimistically — UI flips before the network call.
// 2. Fire the request.
// 3a. Success: reconcile to the server's authoritative value.
// 3b. Failure: roll back to the pre-click value, surface the error.

const onLike = async () => {
  const before = state.value.count;
  state.value = { ...state.value, count: before + 1, pending: true };
  try {
    const r = await fetch("/api/likes", { method: "POST" });
    if (!r.ok) throw new Error("HTTP " + r.status);
    const data = await r.json();
    state.value = { ...state.value, count: data.count, pending: false };
  } catch (e) {
    state.value = { ...state.value, count: before, pending: false, err: String(e) };
  }
};