Why does this optimistic update get reverted by an older fetch?

Hey everyone, I’m working on a small task board and trying to keep the UI snappy with optimistic updates, but I’m hitting a nasty ordering bug where a slower fetch seems to overwrite the newer local state.

let state = { cards: [] };

async function moveCard(id, column) {
  const prev = state.cards;
  state.cards = state.cards.map(c => c.id === id ? { ...c, column } : c);

  try {
    await api.moveCard(id, column);
    const fresh = await api.fetchBoard();
    state.cards = fresh.cards;
  } catch {
    state.cards = prev;
  }
}

What is the practical way to reconcile optimistic UI with follow-up fetches so an older response does not roll back a newer move?

Ellen

@Ellen the fetch is stale. state.cards = fresh.cards blindly replaces state, so if move A finishes after move B, A can paint old data over the newer optimistic move.

A simple fix is to tag each call and only let the latest one commit or roll back:


js
let state = { cards: [] };
let requestId = 0;

async function moveCard(id, column) {
  const myRequestId = ++requestId;
  const prev = state.cards;

  state.cards = state.cards.map(c =>
    c.id === id ? { ...c, column } : c
  );

  try {
    await api.moveCard(id, column);
    const fresh = await api.fetchBoard();

    if (myRequestId === requestId) {
      state.cards = fresh.cards;
    }
  } catch {
    if (myRequestId === requestId) {
      state.cards = prev;
    }
  }
}

The concrete bit is that if an older call fails late, it should not roll back the newer move. Only roll back if that call is still the most recent one.

BayMax