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
// 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) };
}
};