GitHub

Copyright © 2026 Sumit Paul

Design by Sumit Paul•LinkedIn

LinkedIn

Optimistic UI rollback failure

Avatar

Sumeet Kumar Paul

Frontend Engineer

2 min readSource code
Feb 18, 2026

Problem

An optimistic UI update immediately reflects a user action before the server confirms success. If the request fails or multiple toggles occur quickly, the UI can drift away from actual server state.

The failure becomes visible when responses resolve out of order. Because rollback logic depends on previously captured state, rapid interactions can cause the UI to revert to an outdated value that no longer represents user intent.

  • ●User toggles state rapidly.
  • ●UI updates immediately (optimistic update).
  • ●Server responds out of order or fails.
  • ●Rollback logic restores incorrect state.
page.tsx
srcapppracticeoptimistic-ui-rollbackpage.tsx
1const [liked, setLiked] = useState(false);
2
3function handleToggle() {
4  const previous = liked;
5  setLiked(!liked); // optimistic update
6
7  void (async () => {
8    try {
9      await fakeRequest();
10      console.log("[BROKEN] server confirmed");
11    } catch {
12      console.log("[BROKEN] server failed → rolling back");
13      setLiked(previous);
14    }
15  })();
16}

Because the stored 'previous' value represents state at a specific moment in time, it may no longer be valid by the time the request resolves. Rollback logic based on stale baselines introduces subtle state corruption.

Solution

Each optimistic mutation is assigned a request identity. Only the latest request is allowed to commit success or rollback. Outdated responses are ignored instead of mutating state.

Instead of relying on a captured 'previous' value, the UI validates whether the response still matches the most recent user intent. This ensures rollback only occurs when it reflects the current interaction context.

page.tsx
srcapppracticeoptimistic-ui-rollbackpage.tsx
1const requestIdRef = useRef(0);
2const [liked, setLiked] = useState(false);
3
4function handleToggle() {
5  const requestId = ++requestIdRef.current;
6  setLiked((prev) => !prev);
7
8  void (async () => {
9    try {
10      await fakeRequest();
11
12      if (requestId !== requestIdRef.current) {
13        console.log("[FIXED] stale response ignored");
14        return;
15      }
16
17      console.log("[FIXED] server confirmed");
18    } catch {
19      if (requestId !== requestIdRef.current) {
20        console.log("[FIXED] stale failure ignored");
21        return;
22      }
23
24      console.log("[FIXED] server failed → reverting safely");
25      setLiked((prev) => !prev);
26    }
27  })();
28}

By validating request identity before committing success or rollback, the UI remains aligned with the latest user intent even under rapid interaction and network variance.

Optimistic UI improves perceived performance, but without identity validation it can corrupt state under rapid interaction or network latency. Treat async confirmations as untrusted until validated against current intent.

Keywords

optimistic ui, frontend edge case, state rollback bug, react async failure, ui correctness

More Cases

2 min readFeb 20, 2026

State update after route transition

A component starts asynchronous work and then a route transition occurs before the work completes. When the async task resolves, it updates state that no longer matches the active route.

Avatar

Sumeet Kumar Paul

2 min readFeb 16, 2026

Double submit caused by UI state lag

A submit button is disabled only after state updates to 'loading'. Under rapid user interaction, multiple clicks can occur before the UI visually reflects the disabled state.

Avatar

Sumeet Kumar Paul

2 min readFeb 13, 2026

Stale closure in click outside listener

Event listeners defined inside effects capture state from the render during which they were created. If the listener is attached once and depends on state that later changes, it may continue operating on outdated values.

Avatar

Sumeet Kumar Paul

2 min readFeb 10, 2026

State update after unmount during navigation

When a component starts asynchronous work and the user navigates away before it resolves, the async callback may still attempt to update state. At that point, the component no longer exists in the UI tree.

Avatar

Sumeet Kumar Paul