Part V β The Craft Β· Lesson 10.5
State Management + Libraries
In one linePick the library by re-render behaviour and team size, once you've classified the state correctly.
Where you've seen itAdding one item to a cart and watching React DevTools light up the whole header, nav, and sidebar.
Diagram
BLAST RADIUS β the real difference between these tools
CONTEXT
<Provider value={{user, cart, theme}}>
any field changes βββΊ ALL consumers re-render
ββββββ ββββββ ββββββ ββββββ
β ββ β β ββ β β ββ β β ββ β 4 re-renders for a cart change
ββββββ ββββββ ββββββ ββββββ
STORE WITH SELECTORS (Zustand / Redux / Jotai)
useStore(s => s.cart.count)
ββββββ ββββββ ββββββ ββββββ
β ββ β β β β β β β 1 re-render
ββββββ ββββββ ββββββ ββββββDECISION PATH β most of it isn't a library question
what state is it? (6.10)
β
βββ server data βββββββββΊ React Query / RTK Query / SWR
β NOT a global store
βββ URL-shaped ββββββββββΊ the router
βββ form fields βββββββββΊ react-hook-form
βββ genuinely client
β
βββ one subtree, low frequency βββΊ useState / Context
βββ global, high frequency βββββββΊ Zustand Β· Jotai Β·
Redux ToolkitTHE FOUR CLIENT-STATE LIBRARIES, HONESTLY
Zustand tiny API, selectors, no provider β good default
~1KB Β· minimal boilerplate
Jotai atomic, bottom-up, great for
fine-grained derived state
Redux TK conventions, devtools, middleware β large teams,
time-travel debugging complex flows
MobX observable, least boilerplate,
most "magic", implicit reactivityHow it works
- Classify first (6.10). Choosing a library before separating server, URL, client, and form state is how apps end up with a store full of stale API copies.
- Split Context by update frequency if you stay with Context β a slow-changing
ThemeContextand a fast-changingCartContext, never one object. - Select narrowly:
useStore(s => s.cart.count)subscribes to one value; returning an object re-renders on every change unless you add a shallow comparator. - Keep derived data derived. Store the cart items; compute the total in a selector.
- Colocate slices by feature, not by data type, so a feature can be deleted in one directory.
const useCart = create((set) => ({
items: [],
add: (item) => set((s) => ({ items: [...s.items, item] })),
}));
const count = useCart((s) => s.items.length); // only this re-renders
const total = useCart((s) => s.items.reduce((n, i) => n + i.price, 0));Trade-offs
| Library | Choose when | Cost |
|---|---|---|
| Context | Theme, auth, locale | Broadcasts to all consumers |
| Zustand | Default global client state | Fewer conventions for big teams |
| Jotai | Lots of fine-grained derived state | Different mental model |
| Redux Toolkit | Large team, middleware, devtools | Most boilerplate |
Interview angle
"Redux or Context?"
- Neither, usually β most of what people put in Redux is server state and belongs in a query cache.
- Context broadcasts to every consumer, so it fits low-frequency values like theme and locale, not a cart.
- For genuine global client state I'd default to a selector-based store and reach for Redux Toolkit when the team needs strict conventions, middleware, or time-travel debugging.
Recap
- Classify the state first; the library question is smaller than it looks.
- Context re-renders every consumer; selector stores re-render one.
- Store the source, derive everything else.