Filters/dates/sort/page → the URL is the source of truth.
nuqs: useQueryState('city') ≈ useState synced to ?city=.
NOT for secrets, huge blobs, high-freq values.
Context = transport, not a store
Re-render trap ("providers hell"): any value change re-renders EVERY consumer (even ones not using the changed slice). Merge concerns → app-wide re-renders. Splitting into tiny providers = "reinventing Zustand — just use it."
Good for: low-frequency, rarely-changing, widely-read values — theme, locale, auth user. Reach for a store when the value changes often or is read selectively.
Client-store landscape
Lib
Model
Pick when
Zustand
one store + selectors (top-down), no provider
default — simple, granular re-renders, SSR/RSC-ok
Jotai
atoms (bottom-up), subscribe per-atom
many small interdependent pieces
Redux Toolkit
strict unidirectional + middleware + DevTools
large team wants structure/audit; accepts boilerplate
Why Zustand/Jotai beat Context: granular subscriptions — re-render only components reading the changed slice/atom, not all consumers. Criteria: simple · zero/single provider · granular re-render · React-aligned.
"No provider" = store is a module-level object (closure), outside the React tree; hook subscribes directly via useSyncExternalStore, so you just import { useStore } anywhere — no wrapping, no shared ancestor. const useCart = create((set)=>({items:[], add:i=>set(s=>({items:[...s.items,i]}))})) → read with useCart(s=>s.items.length). Exception: SSR/RSC — module singleton leaks across requests, so create store per request + pass via optional provider.
Platform mapping
Results / hotel / prices / bookings
Server → React Query (L08 SWR/dedupe/invalidate-on-book)
City / dates / guests / filters / sort / page
URL → nuqs (SEO + shareable, L09)
Map↔list toggle, modal, hover
Local → useState
Theme / locale / auth user
Shared low-freq → Context
Checkout form + validation
Form → React Hook Form
Lead one-liners (memorize)
Frame "I don't start with a library — I name the state. Server, URL, local, shared, form. Right tool per kind and 90% of the problem evaporates."
Server "Server state isn't app state — it's a cache of data the backend owns. useState/Redux means re-implementing caching by hand. React Query exists so you don't."
URL "Filters and dates are URL state, not React state — on a travel site they must be shareable and crawlable, so the URL is the source of truth (nuqs)."
Context "Context is a transport, not a store: any change re-renders every consumer. Great for theme/user; the moment I'm splitting providers to dodge re-renders, I use a store."
Store "Default Zustand — no provider, selector re-renders. Jotai for atomic state, Redux Toolkit when a big team needs structure. The days of everything-in-Redux are over."
1. "Redux or Zustand?" → reframe: what KIND of state? (server/URL/local/shared/form). Library comes last.
2.Server state = a cache the backend owns (async, stale). NOT Redux/useState → TanStack Query/SWR.
3. Filters/dates/sort/page = URL state (shareable + crawlable), not a global store. Use nuqs.
4.Context re-renders ALL consumers on any change — transport, not store. Good only for low-freq (theme/user).
5. Zustand (selectors) / Jotai (atoms) = granular re-renders; that's why they beat Context for changing state.
6. Zustand = top-down store; Jotai = bottom-up atoms. Neither is for server state.
7. Redux Toolkit when a big team needs strict structure + DevTools, not "by default because it scales."
8. Default stack: TanStack Query + nuqs + Zustand. "Everything in Redux" / "one Context for all" are the anti-patterns.
9. "No provider" ≠ magic: store is a module singleton outside the tree, hook subscribes via useSyncExternalStore. The one time you DO wrap it: SSR/RSC → store per request to avoid cross-request state leaks.