useContext
Passing a value down without bothering every floor on the way
Context is a pneumatic tube through your components: put something in at the top, take it out anywhere below – without handing it down as a prop through five layers.
The problem has a name: Prop drillingPassing a value down through several components that do not need it themselves.→. You want to get the theme from the very top down to a button at the very bottom – and you have to pass it through four components that do not need it themselves. That is what context solves. And nothing else.
Props are like passing something hand to hand: everyone in the chain has to hold the parcel. ContextA kind of pneumatic tube: a value goes in at the top and can be taken out anywhere below.→ is the pneumatic tube: drop it in at the top, it arrives at the bottom – the floors in between never notice.
Three steps
import { createContext, useContext, useState } from "react";
// 1) Create the tube
const ThemeContext = createContext<Theme | null>(null);
// 2) Put something in at the top
function App() {
const [mode, setMode] = useState("light");
const value = { mode, toggle: () => setMode(m => m === "light" ? "dark" : "light") };
return (
<ThemeContext.Provider value={value}>
<WholePage />
</ThemeContext.Provider>
);
}
// 3) Take it out below – however deep
function Button() {
const theme = useContext(ThemeContext);
return <button onClick={theme.toggle}>{theme.mode}</button>;
} Instead of writing useContext(ThemeContext) in 30 places, you write useTheme() once. The benefit: the “is there even a provider?” check lives in one place, and you can rebuild the inside later without touching the callers.
export function useTheme() {
const value = useContext(ThemeContext);
if (!value) throw new Error("useTheme needs a <ThemeProvider> above it");
return value;
} Important: context stores nothing
Context only transports. The state still lives in an ordinary useState inside the component that renders the ProviderThe component that supplies a context value. Everything inside it can reach that value.→. So context does not replace a state manager like Redux – it replaces passing props down.
- +
Theme, language, formatting
- +
Who is signed in and what they are allowed to do
- +
Values inside one component family (
<Tabs>→<Tab>) - +
In short: things that change rarely and that many need
- −
Values that change constantly (mouse position, every key press)
- −
As a cache for server data – there are ready-made libraries for that
- −
To save two levels of props – then the prop is simpler
The one trap: the object is new every time
function App() {
const [user, setUser] = useState(null);
// this object is new on EVERY draw
return (
<UserContext.Provider value={{ user, setUser }}>
<BigTree />
</UserContext.Provider>
);
} function App() {
const [user, setUser] = useState(null);
const value = useMemo(() => ({ user, setUser }), [user]);
return (
<UserContext.Provider value={value}>
<BigTree />
</UserContext.Provider>
);
} Every component reading the context redraws as soon as the value is a different one – and a freshly built object always is. You will meet useMemo in the useMemo & useCallback lesson.
▸ In more depth: splitting, memo and Next.js optional
A proven pattern: two contexts – one for the data (user), one for the actions (logout, setUser). The actions context never changes, so components that only need actions never redraw.
Worth knowing: React.memo does not help against context updates. The value does not arrive through props, it goes around them – so the prop check never sees it.
createContext and useContext only work in a Client componentA component that runs in the browser and can therefore use state, hooks and clicks. Marked with "use client" at the top of the file.→. So the provider goes in a file with "use client" at the top. You can still pass server components in as children – they keep running on the server.
Instead of <ThemeContext.Provider value={…}> you may now write <ThemeContext value={…}>. Both work.
Does it stick?
4 questions on this lesson. Wrong answers show up in your stats.
Now write it yourself
mode and toggle are handed through three levels even though only the bottom one needs them. Replace that with a context plus a useTheme() hook.
- □
ToolbarandPaneltake no props any more - □
ThemedButtongets everything fromuseTheme() - □A click switches between
lightanddark
import { createContext, useContext, useState } from "react"; // TODO: create the context and build a useTheme() hook function ThemedButton({ mode, toggle }: any) { return ( <button onClick={toggle} data-testid="btn"> Mode: {mode} </button> ); } function Panel({ mode, toggle }: any) { return <ThemedButton mode={mode} toggle={toggle} />; } function Toolbar({ mode, toggle }: any) { return <Panel mode={mode} toggle={toggle} />; } export default function App() { const [mode, setMode] = useState("light"); const toggle = () => setMode((m) => (m === "light" ? "dark" : "light")); return ( <div style={{ fontFamily: "system-ui", padding: 16 }}> <Toolbar mode={mode} toggle={toggle} /> </div> ); }