State Management
State management is less about picking a library and more about recognizing that "state" isn't one thing. The most common cause of over-engineered frontends is treating every piece of data the same — cramming server data, UI toggles, and form values all into one global store.
Sort your state into the right category first, and the tooling choice becomes obvious — often "no library needed."
TL;DR
- Local state for component-specific data (
useState). - Global state for data shared across the app (Context, Zustand, Redux).
- Server state for API data — use a data-fetching cache (TanStack Query, SWR), not a store.
- URL state for things that should be shareable/bookmarkable (search params).
- Start local; reach for libraries only when sharing genuinely demands it.
Quick Example
A minimal global store with Zustand — no provider, little boilerplate:
Core Concepts
The four kinds of state
The biggest win is separating server state from client state — remote data has its own concerns (caching, refetching, staleness) that a general store handles poorly.
Client State Libraries
⚠️ Context isn't a performance tool — every consumer re-renders when its value changes. Use it for stable, low-frequency data, and a dedicated store for hot state.
Server State
API data deserves a cache, not a store. Tools like TanStack Query, SWR, and RTK Query give you caching, background refetching, deduping, and mutations out of the box — replacing a pile of useEffect fetch boilerplate. See Data Fetching.
Best Practices
- Start local. Only lift state when more than one component truly needs it.
- Lift to the lowest common parent — not straight to a global store.
- Cache server data with a query library; don't copy it into client state.
- Derive, don't store — compute values from existing state instead of duplicating them.
Common Mistakes
Putting everything in a global store
Duplicating server data into client state
FAQ
Do I actually need Redux?
Most apps don't. Local state plus Context handles a lot, a query library handles server data, and Zustand covers shared client state with minimal ceremony. Choose Redux Toolkit when you have complex, frequently-updated global state and want its structure and DevTools.
Where should a piece of state live?
At the lowest level that needs it. If only one component uses it, keep it local. If a few siblings share it, lift to their common parent. Only promote to a global store when many unrelated parts of the app need it.
What's the difference between client and server state?
Client state is owned by your app (UI toggles, form input). Server state is a cached copy of data that lives on the server — it can go stale and needs refetching, so a query library manages it far better than a plain store.
Why is Context causing re-renders?
Every component consuming a Context re-renders whenever its value changes. Split contexts by concern, memoize the provided value, or move hot, frequently-changing state into a dedicated store like Zustand.
Related Topics
- React — Where local state and hooks live
- Data Fetching — Server-state caching in depth
- Web Performance — Avoiding needless re-renders
- TypeScript — Typing stores and state