FSD Frontend System Design

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.

  • Time16 min
  • DiagramRe-render blast radius per approach
  • Chapter10 Β· Low Level Design

Diagram

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
      β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”˜
diagram
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 Toolkit
diagram
THE 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 reactivity

How it works

  1. 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.
  2. Split Context by update frequency if you stay with Context β€” a slow-changing ThemeContext and a fast-changing CartContext, never one object.
  3. 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.
  4. Keep derived data derived. Store the cart items; compute the total in a selector.
  5. Colocate slices by feature, not by data type, so a feature can be deleted in one directory.
js
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

LibraryChoose whenCost
ContextTheme, auth, localeBroadcasts to all consumers
ZustandDefault global client stateFewer conventions for big teams
JotaiLots of fine-grained derived stateDifferent mental model
Redux ToolkitLarge team, middleware, devtoolsMost 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.