awesome-everything RU
↑ Back to the climb

Queues, Streams, Eventing

Eventual-consistency UX: code reading

Crux Read real React/TanStack Query snippets over an async backend, predict the UX bug, and pick the highest-leverage fix a senior would make first.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min

Eventual-consistency bugs hide in the mutation handler and the retry path. Read the code, predict what the user sees when the consumer lags or the request fails, then choose the fix a senior engineer would make first.

Goal

Practise the loop you run in every async-UX review: read the mutation, predict the divergence between optimistic state and server truth, and reach for the contract-completing fix — snapshot, rollback, reconcile, key — before adding spinners.

Snippet 1 — the half-finished optimistic update

const mutation = useMutation({
  mutationFn: (todo) => api.addTodo(todo),
  onMutate: async (todo) => {
    await qc.cancelQueries({ queryKey: ['todos'] });
    qc.setQueryData(['todos'], (old) => [...old, todo]);
    // returns nothing
  },
  onSettled: () => qc.invalidateQueries({ queryKey: ['todos'] }),
});
Quiz

The request occasionally fails (the consumer rejects the write). What does the user see, and what is the single highest-leverage fix?

Snippet 2 — the retry button

function PayButton({ orderId, amount }) {
  const [status, setStatus] = useState('idle');
  async function pay() {
    setStatus('pending');
    await fetch('/api/charge', {
      method: 'POST',
      body: JSON.stringify({ orderId, amount }),
    });
    setStatus('done');
  }
  // user sees no progress, clicks pay() again on a slow network
  return <button onClick={pay}>Pay</button>;
}
Quiz

On a slow network the user clicks twice. What goes wrong, and what is the right fix?

Snippet 3 — the fake-success toast

async function publish(post) {
  const res = await fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify(post),
  });
  if (res.status === 202) {
    toast.success('Published!');
    await qc.invalidateQueries({ queryKey: ['posts'] });
  }
}
Quiz

The POST returns 202 and a consumer writes the post ~800ms later. What two things are wrong here?

Snippet 4 — the conflict that overwrites

function onIncomingUpdate(remote) {
  // local has unsaved edits; remote arrived from another client
  setDoc(remote); // last write wins, silently
}
Quiz

Two clients edit the same document body. This handler runs on the incoming remote update. What is the senior objection, and the better shape?

Recap

Every async-UX bug is read in the mutation and the retry path: an optimistic update without a snapshot-and-onError rollback strands a failed write on screen; a POST without an idempotency key turns a double-click into a double charge; collapsing 202 into a success toast plus an immediate refetch is fake success racing the consumer; and a silent last-write-wins handler destroys a user’s unsaved edits. Complete the apply-send-reconcile contract, key the retries, show honest pending states, and reconcile conflicts deliberately instead of overwriting.

Continue the climb ↑Eventual-consistency UX: make an async backend feel instant
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.