State Management Architecture — Cheat Sheet

FE Lead prep · ref 13 · §2.7 · ask "what KIND of state?", not "which library?"

The taxonomy (the whole answer)

KindExampleTool
Server (remote/async, a cache)hotels, prices, bookingsTanStack Query / SWR
URL (query params)filters, dates, sort, page, tabnuqs / router
Local (one component)modal open, input, hoveruseState/useReducer
Shared (UI, low-freq)theme, locale, auth userprops → Context → store
Formcheckout + validationReact Hook Form
Split state into concerns → ~90% of the "state problem" disappears. Default stack = TanStack Query + nuqs + Zustand.

Server ≠ client state

URL state (don't forget it)

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

LibModelPick when
Zustandone store + selectors (top-down), no providerdefault — simple, granular re-renders, SSR/RSC-ok
Jotaiatoms (bottom-up), subscribe per-atommany small interdependent pieces
Redux Toolkitstrict unidirectional + middleware + DevToolslarge 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 / bookingsServer → React Query (L08 SWR/dedupe/invalidate-on-book)
City / dates / guests / filters / sort / pageURL → nuqs (SEO + shareable, L09)
Map↔list toggle, modal, hoverLocaluseState
Theme / locale / auth userShared low-freq → Context
Checkout form + validationForm → 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."

Sources: developerway — State Management 2025 (Makarevich) · TanStack Query · nuqs · Zustand · Jotai · Redux Toolkit · react.dev — Context · Kent C. Dodds

Don't fail the interview

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.