Recipe: Optimistic UI updates

Update the UI immediately, then reconcile with the server.

Pattern

// 1. Snapshot current state
const prev = items;

// 2. Apply change immediately
setItems(updated);

// 3. Send request
await fetch("/api/items", {
  method: "POST",
  body: JSON.stringify(newItem),
});

// 4. Rollback on failure
// (wrap in try/catch, restore prev)

Key rules

  • Always keep a rollback snapshot before mutating state.
  • Use a pending flag to show spinners only on the changed row.
  • Deduplicate by request ID to avoid double-inserts on retry.
  • Revalidate with SWR or a background refetch after success.

When to avoid

Skip optimistic updates for destructive actions (deletes, transfers) where a false-positive confirmation is worse than a brief spinner. Always confirm server-side.