Skip to main content

Quiz: State Management

Covers the three categories of state, React Query cache design, targeted invalidation, and optimistic updates. Reference: ADR: State Management · ADR: Idempotency & Optimistic Updates.


Question 1: Three categories of state

Senior

Where does each kind of state live in this app, and what's the one-line rule?
Show answer
  • Server state → React Query cache (wallet data, balances, transactions). It's a cache of someone else's data, so it needs staleness, refetching, and invalidation: exactly what React Query provides.
  • Global client state → Zustand (session, theme). Cross-cutting, but owned by the client.
  • Local UI stateuseState (form inputs, modal open/close). Belongs to one component.

The rule: use the most local storage that works. Form state never goes in Zustand; wallet data never goes in useState. Putting server data in useState means you hand-roll caching and invalidation badly; putting form state in a global store creates spooky action at a distance.


Question 2: Targeted invalidation

Senior

Why are cache keys namespaced and parameterised (e.g. ['accounts', walletId]), and what does that let a transfer do that a flat cache couldn't?
Show answer

Structured keys let you invalidate precisely. A transfer can invalidate ['accounts', fromId] and ['accounts', toId] and leave the transaction feed, balance history, and unrelated wallets alone: only the data that actually changed is refetched.

A flat or coarse cache forces a choice between two bad options: invalidate everything (a thundering refetch and UI flicker on every mutation) or invalidate nothing (stale balances). Parameterised keys give you a scalpel instead of a sledgehammer.


Question 3: Optimistic updates: the three callbacks

Senior

Walk through onMutate / onError / onSettled for an optimistic transfer. Why must you cancelQueries first, and why invalidate in onSettled even on success?
Show answer
  • onMutate: cancelQueries first (so an in-flight refetch can't land after your optimistic write and clobber it), snapshot the current cache, then apply the expected result so the UI updates instantly.
  • onError: restore the snapshot, roll back so the UI never shows a permanently wrong state.
  • onSettled: invalidate the affected keys regardless of success or failure.

Why invalidate even on success: the optimistic value is a guess. The server may have applied a fee, rounded, or reordered. You don't trust your guess or your rollback: on settle you ask the server for the truth. Instant feedback (optimism) plus eventual consistency (invalidate) is the whole pattern.


Question 4: Optimistic state surviving unmount

Staff

A user starts a transfer, navigates away before it resolves, then returns. Why doesn't the rollback break, and what would break it?
Show answer

The snapshot lives in the React Query cache, not in component state. So when the component unmounts on navigation, the snapshot and the in-flight mutation survive: the rollback (or invalidation) still has something to act on when the request settles.

What would break it: holding the snapshot in useState inside the page component. On unmount that state is gone, so an error after navigation has nothing to roll back to, and the cache is left in the optimistic (possibly wrong) state. Stable cache keys + cache-held snapshots are what make it survive.