30% offEnding soon
useOptimisticLoading saved progress…

useOptimistic

Optimistic UI shows a predicted result the instant a user acts — before the server confirms it — then reconciles to the real value once the update settles. You tap "like" and the count jumps immediately; if the server later reports a different number, the UI quietly corrects itself. Your job is to implement useOptimistic, a React hook modeled on React 19's useOptimistic, that layers a temporary optimistic value on top of a real one and clears it automatically once the real value catches up.

The hook takes the real, committed state and an updateFn, and returns the value to render plus a function to apply an optimistic update. While an optimistic input is pending, the returned value is updateFn(state, input). The moment the real state you pass in changes — the server responded, the caller committed — the optimistic layer is discarded and you show the real state again.

Signature

useOptimistic<State, Input>(
  state: State,                                     // the real, committed value (passed in each render)
  updateFn: (current: State, input: Input) => State // pure: fold an optimistic input onto the current value
): [
  optimisticState: State,                // what to render: state, or the overlay while pending
  addOptimistic: (input: Input) => void  // apply an optimistic update; identity is stable
];

Examples

// Base state is 40 likes. The user taps like.
const [likes, addLike] = useOptimistic(40, (current, delta) => current + delta);

likes;            // 40  — nothing pending, shows the real value
addLike(1);       // apply an optimistic +1
likes;            // 41  — the guess, shown immediately (updateFn(40, 1))
// The real state commits to 42 (the server's authoritative count). Re-render
// with the new base; the optimistic overlay is discarded automatically.
const [likes] = useOptimistic(42, (current, delta) => current + delta);

likes;            // 42  — NOT the stale 41 guess. The overlay cleared on its own.

Notes

  • Derived, never storedoptimisticState is computed from state plus any pending input at render time. Keeping it as its own useState is the trap: it never reconciles.
  • Reconcile on base-state change — when the state prop changes, drop the overlay and show the new base. This is the reconciliation trigger you must implement.
  • updateFn is pure — it takes the current value and an input and returns the next value, without mutating its arguments.
  • Optimistic updates compose — calling addOptimistic more than once layers the inputs in order, each folded onto the previous overlay.
  • Stable addOptimistic — its identity does not change across re-renders, so it is safe in an effect dependency array or a memoized child.
  • No real actions needed — you do not need React transitions or actions. Model "the real value committed" as a new state prop; the reconcile watches that prop.