When to Use Context
Context solves prop drilling but is not a general state manager.
Context is powerful, but it is not the answer to every state problem. Reach for it thoughtfully.
Good fits
- Theme, locale, or the current authenticated user.
- Values many components at different depths need to read.
Watch out for
- Every consumer re-renders when the value changes, so avoid putting rapidly-changing state in one giant context.
- Split unrelated data into separate contexts to limit re-renders.
- Pair context with
useReducerto hold both state and a dispatch function.
Example
import { createContext, useReducer, useContext } from 'react';
const CountContext = createContext(null);
function reducer(state, action) {
return action === 'inc' ? state + 1 : state;
}
function Provider({ children }) {
const [count, dispatch] = useReducer(reducer, 0);
return (
<CountContext.Provider value={{ count, dispatch }}>
{children}
</CountContext.Provider>
);
}
function Display() {
const { count } = useContext(CountContext);
return <p>{count}</p>;
}When to use it
- A theme preference is stored in context because every leaf component in the app needs it, making it a classic global-read-only use case that context handles well.
- A real-time collaboration document avoids context for its editor state because the editor updates hundreds of times per second and context re-renders all consumers on every update.
- A wizard component uses local component state shared via props instead of context because the data only flows between three tightly coupled steps and does not need to be global.
More examples
Good context candidate: auth user
Distributes the authenticated user object globally β a stable, infrequently-changing value ideal for context.
import { createContext, useContext } from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ user, children }) {
return <AuthContext.Provider value={user}>{children}</AuthContext.Provider>;
}
export const useAuth = () => useContext(AuthContext);
// Any component can read the logged-in user:
// const user = useAuth();Poor context use: frequently updated
Shows why high-frequency state should not go in context; pass it directly to the few components that need it.
// BAD β cursor position updates on every mouse move;
// every context consumer re-renders
const CursorContext = createContext({ x: 0, y: 0 });
// BETTER β pass cursor only to the component that needs it
function Canvas({ children }) {
const [pos, setPos] = React.useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPos({ x: e.clientX, y: e.clientY })}>
<Crosshair x={pos.x} y={pos.y} />
{children}
</div>
);
}Split contexts by update frequency
Splits user and cart into separate contexts so cart updates do not re-render components that only consume user.
// Separate the rarely-changed user from the often-changed cart
const UserContext = createContext(null);
const CartContext = createContext([]);
function AppProviders({ user, cart, children }) {
return (
<UserContext.Provider value={user}>
<CartContext.Provider value={cart}>
{children}
</CartContext.Provider>
</UserContext.Provider>
);
}
Discussion