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.