All posts
React2026-04-02 · 5 min

Server state is not UI state

Most tangled React codebases I have inherited share one root cause: server data living in component state.


Two kinds of state

UI state is local, ephemeral, and yours: an open dropdown, a form draft, a hovered row. Server state is a cache of someone else's truth — it is stale by default and shared between screens.

The symptom

const [users, setUsers] = useState([]);
useEffect(() => { fetch('/api/users').then(r => r.json()).then(setUsers); }, []);

Every screen re-implements loading, error, refetch, and invalidation. None of them agree.

The fix

Let a cache own server state and keep useState for genuinely local things.

  • Query keys describe the resource, not the screen.
  • Invalidate on mutation instead of manually patching arrays.
  • Derive rendering state from the query status rather than a separate boolean.

Result

Components shrink to rendering plus intent. The interesting logic moves to one place you can actually reason about.