React state: deciding where it lives before deciding how
Redux vs Zustand vs TanStack Query is the wrong first question. Ownership is the first question — the library follows from it.
Every few months the argument restarts: Redux or Zustand or Jotai or context. It is mostly the wrong argument. The library is a consequence; the decision is where each piece of state actually lives.
The four homes of state
- Server state — data whose source of truth is your API: lists, profiles, balances. It is a cache, and it wants cache semantics: fetching, invalidation, staleness. TanStack Query exists precisely for this, and moving server data out of Redux stores deleted more code from my projects than any other single change.
- URL state — filters, tabs, pagination, the selected item. If a user would want to share or bookmark it, it belongs in the URL, not in a store. The back button is a feature you get for free.
- Local state — an open dropdown, a controlled input, a hover. useState, in the component, and it should embarrass nobody. Lifting it "just in case" is how prop-drilling starts.
- Shared client state — the small remainder: theme, session user, a multi-step form draft. This is the only tier that earns a store, and it is usually tiny. Zustand's few-lines-per-slice honesty fits it well.
The audit that pays for itself
Take your current global store and label every field with one of the four homes. In every codebase where I have done this, the majority was server state pretending to be client state — cached responses with hand-written loading flags, going stale in a corner.
Pick ownership first. After that, the library debate mostly answers itself — and the store you keep ends up small enough that it barely matters which one you chose.
Enjoyed this one?
I write about what I build. Let's talk about yours.