Frontend•Jun 2026•3 min read

Context Api vs Zustand

React's built-in Context API versus Zustand for client state. One ships with React and re-renders the world; the other is 1KB and surgical. We pick a winner.

The short answer

Zustand over Context Api for most cases. Context broadcasts every change to every consumer with no selective subscription, so it rots into re-render soup the moment your app grows.

  • Pick Context Api if your state is tiny, near-static, and read by a handful of components — theme, locale, current user, an auth token. Context is built in, zero dependencies, and perfectly fine for low-frequency values
  • Pick Zustand if have state that changes often, is read by many components, or you're sick of memoizing context values and splitting providers to dodge re-renders. This is most apps
  • Also consider: Server state? Neither. Reach for TanStack Query or RTK Query — Context and Zustand are both client-state tools, and stuffing fetched data into either is a category error people make constantly.

— Nice Pick, opinionated tool recommendations

The core difference

Context API is a dependency-injection mechanism that React bolts on for free. It moves a value down the tree without prop drilling — that's its entire job. It is NOT a state manager, no matter how many tutorials pretend otherwise. Zustand is a purpose-built store: you create state outside React, components subscribe to slices of it via selectors, and only the components reading a changed slice re-render. The architectural gap is selective subscription. Context has none — every consumer re-renders when the provider value changes by reference, full stop. Zustand was designed around exactly this problem. So the comparison isn't 'simple vs complex,' it's 'a wire vs a switchboard.' People reach for Context because it's already there, then spend weeks engineering around its one fatal limitation. That's the whole story, and it decides almost everything below.

Performance and re-renders

This is where Context earns its reputation. A Context value is a single object; change one field and React re-renders every consumer of that provider, regardless of which field they read. The standard 'fixes' are ugly: split into multiple providers, wrap values in useMemo, memoize every consumer with React.memo, or hand-roll a useContextSelector. You are now reimplementing Zustand badly. Zustand sidesteps all of it: const count = useStore(s => s.count) subscribes to that slice only, and a change to s.user won't touch this component. It uses strict-equality (or your custom comparator) to decide. For a settings panel that updates twice a session, Context's broadcast costs nothing. For a cart, a canvas, a live dashboard, or anything updating per-keystroke, Context will tank your frame budget and you'll feel it. Zustand simply doesn't have this failure mode.

Developer experience

Context's ergonomics are deceptively rough. You write a context, a provider, a custom hook, often a reducer, and you nest providers until your tree looks like a Russian doll. Updating state from deep children means threading dispatch through — more boilerplate, the exact thing Context was supposed to kill. Zustand is almost rude in its simplicity: one create call returns a hook, you read with selectors, you write by calling actions defined in the store, and there's no provider to mount at all. State lives in a module, so you can read or mutate it outside React too — handy in event handlers, web socket callbacks, or tests. Middleware for persistence, immer, and devtools is a one-line wrapper. Context gives you none of that; you build it. The only DX point Context wins is 'nothing to install,' which matters for about a day.

When Context actually wins

I'm not pretending Context is useless — it wins a real, narrow band. If a value is set once and rarely changes — theme, locale, feature flags, the authenticated user, a DI handle to some service — Context is the correct, idiomatic tool and pulling in Zustand would be over-engineering. Library authors especially should prefer Context: shipping a store dependency onto consumers is presumptuous, while Context is zero-footprint and composable. Context also pairs naturally with useReducer for genuinely local, scoped state that shouldn't be global at all — Zustand's module-level store is the wrong shape for 'this state belongs to this subtree.' So the honest rule: Context for low-frequency, injected, or scoped values; Zustand for shared, mutating application state. Most teams misclassify their state as the former when it's screamingly the latter, then blame React for being slow.

Quick Comparison

FactorContext ApiZustand
Selective subscriptionsNone — all consumers re-render on any value changePer-slice selectors with custom equality
Bundle cost0 KB (built into React)~1 KB gzipped
BoilerplateProvider + custom hook + often a reducer; nested provider treesOne create() call, no provider needed
Use outside ReactNo — coupled to the component treeYes — read/write the store anywhere
Best fitLow-frequency injected/scoped values (theme, user, DI)Shared, frequently-changing app state

The Verdict

Use Context Api if: Your state is tiny, near-static, and read by a handful of components — theme, locale, current user, an auth token. Context is built in, zero dependencies, and perfectly fine for low-frequency values.

Use Zustand if: You have state that changes often, is read by many components, or you're sick of memoizing context values and splitting providers to dodge re-renders. This is most apps.

Consider: Server state? Neither. Reach for TanStack Query or RTK Query — Context and Zustand are both client-state tools, and stuffing fetched data into either is a category error people make constantly.

🧊
The Bottom Line
Zustand wins

Context broadcasts every change to every consumer with no selective subscription, so it rots into re-render soup the moment your app grows. Zustand gives you selector-based subscriptions, no provider tree, and a smaller bundle. It's the right default for any real state.

Related Comparisons

Disagree? nice@nicepick.dev