React Context — when does it cause re-renders, and how do you avoid them?
Every consumer of a context re-renders whenever the provider's `value` changes by reference. Stabilize the value, split contexts, or use a selector library (Zustand, use-context-selector) for high-churn state.
Context is React's built-in dependency injection. It's perfect for low-frequency values — theme, locale, current user, feature flags. It's wrong for high-frequency state — mouse position, form fields, anything that changes every keystroke — because every consumer re-renders on every value change.
The mechanism: <Provider value={…}> triggers a re-render in every component that calls useContext, regardless of which part of the value they read. There's no built-in selector.
Three fixes, in increasing power:
- Stabilize the value. Wrap in
useMemoso the reference only changes when underlying data does. Catches the most common bug — inline object literals creating a new reference every render. - Split contexts. Separate
UserContextfromUserActionsContextso a re-render of the user object doesn't re-render every component that only needslogout(). Actions barely change; data does. - Selector pattern.
use-context-selector(or graduate to Zustand/Redux) lets consumers subscribe to a specific slice. Only components reading that slice re-render. This is what state libraries solve out of the box.
Default: use Context for things that rarely change. Reach for a real store the moment you find yourself memoizing aggressively or splitting contexts to avoid render storms — that's the signal you've outgrown raw Context.
Code
Follow-up questions
- •How does use-context-selector work under the hood?
- •Why is Zustand cheaper than Context for high-churn state?
- •When would you NOT split contexts?
Common mistakes
- •Inline `value={{ ... }}` literal that re-renders the whole subtree every parent render.
- •Stuffing every piece of app state into one giant Context.
- •Using Context for mouse/scroll position — should be a ref or external store.
Performance considerations
- •Selectors with strict equality (Zustand, Redux) avoid re-renders that Context can't.
Edge cases
- •Memoizing a context value with deps that miss a captured variable causes silent staleness.
- •Multiple providers stack — `useContext` reads the nearest provider above.
Real-world examples
- •Theme + locale + auth typically live in Context. Form state, query results, and live data go in a store or React Query.