AcademyReact
React Context and When to Use It
Context skips prop drilling for broadly needed values, but every consumer re-renders on any change to it — the cost most people miss until it shows up in a profiler.
3 min readUpdated 3 exercises
The mechanism
const ThemeContext = createContext('light');
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return <Button />; // never touches theme
}
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click</button>;
}Button reads theme directly from the nearest ThemeContext.Provider above it, however many layers away that is. Toolbar sits in between and never mentions theme at all — this is exactly the prop-drilling problem Context exists to remove for values a large, unpredictable part of the tree needs: the current theme, the signed-in user, the active locale.
What Context is not
Context is not a general-purpose state-management replacement for something like Redux or Zustand. It has no built-in way to update a value from deep in the tree without also passing a setter function down through it, no selectors, no way for a consumer to subscribe to only part of the value — a consumer either reads the whole context value or none of it. For state that changes often and is read by many components that each only care about a slice of it, a dedicated store, not Context, is the right tool.
The cost almost everyone misses
Every component that calls useContext on a given context re-renders whenever that context's value changes — even if the component only reads one field out of an object the provider is handing down:
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
const value = { user, theme, notifications, setUser, setTheme, setNotifications };
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}One context, three unrelated pieces of state. A component that only reads theme still re-renders every time notifications changes, because from React's point of view the context's value changed — it does not know, or care, that the component consuming it never looked at the field that actually moved. In an app where notifications arrive every few seconds, every themed component pays for a re-render it has no reason to need.
The fix: split by change frequency, not by topic
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
<NotificationsContext.Provider value={notifications}>
{children}
</NotificationsContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>Three contexts instead of one. A component reading only theme now re-renders only when theme changes — notifications arriving no longer touches it. The grouping question is not "do these values belong to the same feature" but "do they change at the same rate": bundle values that change together, separate the ones that don't.
The second, smaller trap: a fresh value object every render
// New object every render, even when user and theme haven't changed
return <AppContext.Provider value={{ user, theme }}>{children}</AppContext.Provider>;{ user, theme } is a new object literal on every call to this component, so every consumer sees "the value changed" — by reference — on every parent re-render, whatever user and theme actually did. Memoizing the value object so its identity only changes when its contents do closes this gap:
const value = useMemo(() => ({ user, theme }), [user, theme]);
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;What to say in an interview
Context solves prop drilling for values a broad, unpredictable part of the tree needs — it does not solve frequent, fine-grained state updates, because every consumer re-renders on any change to the value, whole object at a time. The fix when that becomes a real cost is splitting one context into several by how often each piece changes, and memoizing the value object so an unrelated parent re-render doesn't hand consumers a "new" value for free.
Now practice it
2 challenges · 1 quizReading this page is the cheap half. These are the exercises that make you use it.
- createContext + ProviderProBuild createContext and a Provider yourself — the re-render-on-any-change behavior stops being abstract.ChallengeSolve · MediumChallengeSolve · Medium
- useLocalStorage HookProA hook often paired with Context for persisted global state, and a good case for splitting concerns.ChallengeSolve · MediumChallengeSolve · Medium
- State Management · Global StateHarder state-management questions, including where Context stops being the right tool.QuizTake · HardQuizTake · Hard
Read next
Lifting State Up and Prop Drilling
Why two components that need to stay in sync must share state owned by their closest common ancestor, and when passing that state down several layers becomes a real problem.
3 min
Choosing Between useReducer and useState
useReducer is useState generalized: a pure reducer plus dispatch instead of a raw setter, and when that trade is worth making.
3 min