- Home
- /
- Tutorials
- /
- React Tutorial
- /
- useContext Explained
Context
useContext Explained
Context is for values that many components at different depths need - a theme, the current user, a language. It is not a state manager.
The problem it solves
Passing a value through four components that do not use it is called prop drilling. Every layer has to declare and forward a prop it does not care about, and adding a fifth means touching all of them.
The three pieces
createContext(defaultValue)makes the channel.<Context.Provider value={...}>supplies a value to everything inside it.useContext(Context)reads the nearest provider's value.
Skipping the middle layers
jsx
const ThemeContext = React.createContext("light")
function DeepButton() {
// Reads the theme without any component in between passing it down.
const theme = React.useContext(ThemeContext)
const dark = theme === "dark"
return (
<button style={{
background: dark ? "#0f172a" : "#e2e8f0",
color: dark ? "#ffffff" : "#0f172a",
border: 0, borderRadius: 6, padding: "6px 12px",
}}>
Theme is {theme}
</button>
)
}
function Middle() {
// Knows nothing about the theme.
return <div style={{ padding: 10 }}><DeepButton /></div>
}
function App() {
const [theme, setTheme] = React.useState("light")
return (
<ThemeContext.Provider value={theme}>
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Toggle theme
</button>
<Middle />
</ThemeContext.Provider>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)Middle has no props at all, yet the button below it follows the theme. That is the entire point.
The default value
The argument to createContext is used only when a component reads the context with no provider above it. It is a fallback, not an initial value, and a sensible one makes components usable in isolation.
Passing state down
Context carries any value, including a setter. Providing an object with both the value and a way to change it is the usual shape.
Value and updater together
jsx
const CountContext = React.createContext(null)
function Display() {
const { count } = React.useContext(CountContext)
return <p>Count is {count}</p>
}
function Controls() {
const { setCount } = React.useContext(CountContext)
return (
<div style={{ display: "flex", gap: 6 }}>
<button onClick={() => setCount((c) => c - 1)}>−</button>
<button onClick={() => setCount((c) => c + 1)}>+</button>
</div>
)
}
function App() {
const [count, setCount] = React.useState(0)
return (
<CountContext.Provider value={{ count, setCount }}>
<Display />
<Controls />
</CountContext.Provider>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)When not to use it
- Passing down one or two levels. Props are clearer; context hides where a value came from.
- Frequently changing values. Every consumer re-renders on every change - a value updating per keystroke will be felt.
- As a state manager. Context distributes a value; it does not manage one. The state still lives in a component above.
The honest rule: reach for context when prop drilling has become genuinely painful, not in anticipation of it. Three levels of props is not painful.
