Context — a Path to Hand Values to Distant Components
How Context solves the fatigue of drilling Props down step by step. The three stages of createContext, Provider, and useContext — and when to use it versus when to hold back.
Up to last time we looked at the values a single component handles on its own (State and Ref). Now let’s widen the view to several components sharing a value. We said Props hand a value from parent to child — but there are moments when that isn’t enough. What fills that gap is Context.
Prop drilling — the fatigue of carrying a value down like stairs
Props are passed one step at a time. Parent → child → grandchild, one level at a time. But what if a value set at the top (say, a dark/light theme) has to be used by a button five levels down?
<App theme={theme}>
<Layout theme={theme}> {/* Layout doesn't even use theme… */}
<Sidebar theme={theme}> {/* Sidebar just passes it along… */}
<Button theme={theme} /> {/* the one that truly needs it is here */}
The intermediate Layout and Sidebar have to keep receiving and passing down theme without even using it, just to get it further down. Carrying a value tier by tier like this is called prop drilling. The deeper the layers, the messier the code, and the harder it is to tell what each middle component is even carrying.
Context lets you skip these stairs. Broadcast a value once at the top, and a component at any depth below can pull it directly, without going through the middle.
The three stages of Context
You use Context in exactly three stages.
① Create it — createContext
import { createContext } from 'react';
export const ThemeContext = createContext('light'); // default value
What createContext returns is not a component but a single “conduit object.” This object links the side that broadcasts a value to the side that pulls it out.
② Broadcast it — wrap with <Context>
function MyPage() {
return (
<ThemeContext value="dark">
<Form /> {/* anything at any depth in here can pull 'dark' */}
</ThemeContext>
);
}
Inside the region wrapped by ThemeContext, that value can be subscribed to regardless of level.
Older code uses the form
<ThemeContext.Provider value="dark">. Since React 19 it’s been shortened so you can write<ThemeContext value=...>directly, as above. They’re the same thing.
③ Pull it out — useContext
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext); // pulled directly, no middle steps
return <button className={theme}>Button</button>;
}
No matter how deep Button sits, one line of useContext(ThemeContext) receives the broadcast value directly. All those theme props climbing up and down the stairs disappear.
Not just constants — state and functions too
The value you broadcast needn’t be a fixed constant. Combine it with State and you can even change the value from below.
function MyPage() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext value={{ theme, setTheme }}>
<Form />
</ThemeContext>
);
}
Send theme and setTheme down together, and a lower component can not only read the value but change it. A classic example is an auth context that broadcasts the logged-in user info along with a login() function.
Providers can also be nested, and in that case the nearest one wins.
<ThemeContext value="dark">
<ThemeContext value="light">
<Footer /> {/* subscribes to the nearest 'light' */}
</ThemeContext>
</ThemeContext>
When to use it, when to hold back
Context is powerful but not a universal tool. It shines for broadcasting values the whole app uses broadly — theme, the logged-in user, language settings. Conversely, if you pull every value that only needs to go two or three levels into Context, the origin of the value gets blurry and harder to trace. Think of it as the tool you reach for when prop drilling actually becomes a pain.
One more thing. When a Context value changes, the lower components subscribing to it re-render. The wider the region a Context wraps, the larger the ripple of these re-renders can be, and there comes a point where you have to mind performance. How to cut down on “more re-renders than necessary” is exactly the subject of the next article. Finally, we’ll look at rendering optimization — useMemo, useCallback, and memo.