Context
Share state across component trees with Context API patterns that scale without excessive rerenders.
When Context Fits
Context solves prop drilling for data many components need — theme, locale, auth session, feature flags. It is not a replacement for all global state — frequent fine-grained updates across many consumers perform poorly without splitting.
Reach for Context when data is truly global or semi-global and update frequency is moderate. For high-frequency updates (mouse position, animation frames), use refs, external stores, or state libraries.
- Theme, auth, locale are good fits
- Not for high-frequency updates
- Alternative to deep prop drilling
Provider Design
Colocate Provider near consumers that need it — not always at app root unless truly global. Split state and dispatch into separate contexts so components needing only dispatch do not rerender on state changes.
Memoize value objects: `const value = useMemo(() => ({ user, login }), [user, login])`. New object literals each render force all consumers to rerender regardless of memo.
- Split state and dispatch contexts
- Memoize provider values
- Place providers at lowest useful level
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;Custom Context Hooks
Export `useTheme()` that calls useContext and throws if provider missing — fail fast with clear errors instead of silent undefined behavior. Type context as null default and narrow in hook.
This pattern encapsulates context access and enables swapping implementation later without changing consumers.
- Custom hook wraps useContext
- Throw if provider missing
- Encapsulate access pattern
function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme requires ThemeProvider');
return ctx;
}Context vs External Stores
Zustand, Jotai, and Redux use subscription models avoiding rerender of unrelated components. Context rerenders all consumers when value reference changes unless split carefully.
Start with Context for simplicity. Migrate to external stores when profiling shows context-driven rerender bottlenecks or when you need devtools time-travel and middleware.
- Context rerenders all consumers on value change
- Stores offer granular subscriptions
- Migrate when profiling demands it
Testing Context
Wrap components under test with Provider supplying fixture values. Create test utilities `renderWithTheme(ui)` that wrap default providers. Avoid testing context implementation — test consumer behavior.
Mock context providers sparingly — prefer real providers with test data for integration confidence.
- Test utilities wrap providers
- Fixture values for isolation
- Test behavior, not context internals