Skip to content

Blog / Optimistic Ui

Optimistic UI rollback failure

Sumeet Kumar Paul2 min read
The symptom
After rapid toggles and a failed request, the UI shows the wrong value.
The cause
Rollback restores a value captured before newer interactions happened.
The fix
Allow only the latest request to confirm or roll back.

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-rollback-failurepage.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-rollback-failurepage.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.

Check your own code

  1. 01Is rollback based on a value captured before the latest interaction?
  2. 02What should the UI show if an older request fails?
  3. 03Have you tested rapid toggling against a failing endpoint?
  • optimistic ui
  • frontend edge case
  • state rollback bug
  • react async failure
  • ui correctness
View the source on GitHub ↗

Written by Sumeet Kumar Paul

Senior Frontend Engineer from India. Frontend Lab is where I reproduce real interface bugs and work through the fix.

Share article:

Need a hand?

I build frontends like this for a living.

Get in touch

More from the lab