Runnable lesson · press Try it on the examples to edit and run them here.
Props pass data from parent to child. But when many components deep in the tree need the same data (the current user, the theme, the language), passing props through every level becomes tiring. This is called prop drilling. Context lets a component share data with everything below it, without passing it step by step.
The three steps
- Create a context with
createContext(defaultValue). - Provide a value with
<MyContext value={...}>around the part of the tree that needs it. - Read it in any child with
useContext(MyContext).
const ThemeContext = React.createContext("light"); // 1. create (default is "light")
function ThemedButton() {
const theme = React.useContext(ThemeContext); // 3. read
return (
<button style={{ background: theme === "dark" ? "#222" : "#eee", color: theme === "dark" ? "#fff" : "#000" }}>
Theme: {theme}
</button>
);
}
function Toolbar() {
// No theme prop here: Toolbar does not need to know about it
return <div><ThemedButton /></div>;
}
function App() {
const [theme, setTheme] = React.useState("light");
return (
<ThemeContext value={theme}> {/* 2. provide */}
<Toolbar />
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Toggle theme</button>
</ThemeContext>
);
}
Toolbar never receives or passes the theme, but ThemedButton deep inside reads it directly. When theme state changes, the provider re-renders and every component that reads the context updates.
React 19 syntax note: you can render the context itself as the provider: <ThemeContext value={theme}>. In React 18 and earlier you write <ThemeContext.Provider value={theme}>. The old form still works.
The default value
The value in createContext("light") is used only when there is no provider above the component. It is useful for tests and for components used standalone.
Putting state and updater in context
A context value can be an object with data and functions:
const UserContext = React.createContext(null);
function Greeting() {
const { user } = React.useContext(UserContext);
return <p>Hello, {user ? user.name : "Guest"}</p>;
}
function LoginButton() {
const { user, login, logout } = React.useContext(UserContext);
return user ? <button onClick={logout}>Log out</button> : <button onClick={login}>Log in</button>;
}
function App() {
const [user, setUser] = React.useState(null);
const value = {
user,
login: () => setUser({ name: "Ada" }),
logout: () => setUser(null),
};
return (
<UserContext value={value}>
<Greeting />
<LoginButton />
</UserContext>
);
}
Using use to read context
React 19 also lets you read context with the use function. Unlike hooks, use can be called conditionally: const theme = use(ThemeContext);. useContext remains perfectly fine.
When to use context
Context is a good fit for rarely changing, widely needed data:
- Current theme, language and locale
- The signed-in user
- A router’s current location (React Router uses context)
- A small shared store, like a cart (with care)
When not to use it
- To avoid passing a prop through two levels. That is fine as it is. Passing props explicitly makes data flow easy to follow.
- For fast-changing data such as mouse position or text in an input. Every consumer re-renders when the value changes.
- As a replacement for composition. Often passing
childrenor JSX as a prop (see Component Composition) removes the need for drilling without context.
Performance note
When the provider’s value changes (compared with Object.is), every component that reads that context re-renders. If you pass a new object literal each render, as above, consumers re-render whenever the provider does. For big apps, keep the value stable with useMemo, and split unrelated data into separate contexts. Context API covers patterns for this.
Common mistakes
- Using a context without a provider and getting the default value by accident.
- Putting too much into one context, causing needless re-renders.
- Forgetting to import and share the same context object. Create it in one file and import it where needed.
Key points
createContext+ a provider +useContextshare data with a whole subtree.- When the provided value changes, all consumers re-render.
- Use it for broad, slowly changing data, not as a first reflex.