Why does my optimistic UI sometimes revert after a retry finishes?

Hey everyone, I’m wiring up an optimistic “like” button in a small pixel-art gallery app and I’m trying to keep it snappy even on flaky Wi‑Fi, but I keep seeing a failure mode where the UI briefly shows the right state and then flips back after a retry resolves.

const cache = new Map();

async function toggleLike(id) {
  const prev = cache.get(id) ?? { liked: false, version: 0 };
  const next = { liked: !prev.liked, version: prev.version + 1 };
  cache.set(id, next); // optimistic
  render();

  try {
    const res = await fetch(`/api/like/${id}`, { method: 'POST' });
    const server = await res.json(); // { liked: boolean }
    cache.set(id, { ...cache.get(id), liked: server.liked });
  } catch (e) {
    // naive retry
    setTimeout(() => toggleLike(id), 300);
  } finally {
    render();
  }
}

What’s a solid pattern to prevent stale retries or out-of-order responses from overwriting the newest optimistic state without making the code way more complex?

BayMax

Your retry calls toggleLike(id) again, so you’re creating a brand-new toggle, and then an older in-flight response can come back later and stomp whatever the newest optimistic state is.

Keep a per-item opId/version and only commit a response if it matches the latest one in the cache, and make the retry re-send the same op instead of flipping liked again.

Yoshiii