Lesson 17 of 31
Context and useContext
Sharing a value across many components without threading it through every layer of props — createContext, Provider, useContext, and the re-render cost that limits it.
You met the problem this lesson solves at the end of the "lifting state up" lesson: state shared by components that are many layers apart forces every component in between to accept and forward props it doesn't actually use itself. Context is React's mechanism for skipping that.
The three pieces
import { createContext, useContext } from "react";
// 1. Create the context (usually once, in its own module)
const ThemeContext = createContext("light");
// 2. Provide a value to a subtree
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 3. Read it in any descendant, however deep
function Toolbar() {
return <ThemedButton />; // Toolbar itself never touches theme
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={`btn-${theme}`}>Save</button>;
}ThemedButton reads theme directly, despite Toolbar sitting in between
and never mentioning theme at all — that's the whole payoff. The argument
passed to createContext ("light" here) is only the fallback used if a
component calls useContext with no matching Provider above it in the
tree; whenever a Provider is present, its value wins.
Context pairs naturally with state
Providers commonly hand down not just a value but also a setter, so descendants can both read and update the shared value:
const ThemeContext = createContext(null);
function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function ThemeToggle() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Switch to {theme === "light" ? "dark" : "light"}
</button>
);
}This is the same "lift state up, pass a setter down" pattern from before — context just changes how the value reaches descendants, from prop-by-prop to direct.
What context is genuinely good for
Context earns its keep for values that are: read by many components spread throughout the tree, and don't change especially often — the textbook examples are the current theme, the logged-in user, the active locale, or a router's current location. These are exactly the kind of "ambient" data that would otherwise force every component between the top of the tree and wherever it's needed to forward a prop it never uses.
The re-render cost
Context doesn't track which specific field of its value a given consumer
actually reads — it only tracks whether the value passed to Provider
changed at all. When it does, every component calling useContext for
that context re-renders, whether or not the part it cares about actually
changed.
function App() {
const [user, setUser] = useState({ name: "Priya" });
const [mouseX, setMouseX] = useState(0); // changes constantly, e.g. on mousemove
// If these were combined into one context value, every mousemove
// would re-render every component reading the context — including
// ones that only ever cared about `user`.
return (
<UserContext.Provider value={user}>
<MainApp />
</UserContext.Provider>
);
}This is exactly why context is a poor fit for something that changes on every keystroke or every mouse move — every consumer downstream re-renders on every change, regardless of relevance. For high-frequency values, local state (kept as close as possible to where it's actually used) is the right tool; context is for comparatively stable, broadly-needed values.
Try it yourself
What to remember
- Context is createContext + Provider + useContext — a way for any descendant to read a value directly, without every layer in between forwarding it as a prop.
- It's the right tool for broadly-needed, comparatively stable values: theme, current user, locale.
- Every consumer of a context re-renders whenever the Provider's value changes, regardless of which part of it a given consumer actually reads.
- Context is a poor fit for high-frequency-changing values — keep those as local state instead.
Check yourself
4 questions · pass 3/4 to unlock useReducer for Complex State
1.What problem does context solve that lifting state up doesn't solve well?
2.What three pieces does using context typically involve?
3.What happens to every component that calls useContext(SomeContext) when that context's Provider value changes?
4.Why is context described as unsuitable for something like frequently-updating form input state?
4 left to answer