Reactadvanced 9 min
Share data with many components without passing props through every level.
What "prop drilling" is, how createContext, a Provider and useContext solve it, and when context is the right tool.
Some data is needed all over an app: the logged-in user, the theme, the language. Passing it as a prop through ten layers of components that do not use it is tedious and fragile. Context makes it available directly to whoever needs it.
Prop drilling is passing a prop through components that do not need it, just to reach a deep child. Context removes that.
Three steps:
const ThemeContext = createContext("light"); The argument is a default value.<ThemeContext.Provider value={theme}> ... </ThemeContext.Provider>. Every component below can access it.const theme = useContext(ThemeContext);When the provided value changes, every component that uses it re-renders.
Context is great for data that changes rarely and is needed widely (theme, user, language). For fast-changing or complex data, consider a state library, and keep local state local.
You often combine context with state: the provider component holds the state and provides both the value and a function to change it.
createContext(null) makes the context object.ThemeProvider keeps dark in state and provides an object with the value and a toggle function.Layout sits in the middle and passes nothing down. It does not know about the theme.ThemeButton and Card call useContext and receive the provided value directly.Press the button in the preview. Then add a language setting to the context value, such as lang: "en", and show it in Card.
useContext outside a Provider, which returns the default value (here null) and crashes on destructuring.useMemo.createContext, Provider and useContext share data without prop drilling.Next: put everything together in a small project.
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [dark, setDark] = useState(false);
const value = { dark, toggle: () => setDark(!dark) };
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}
function ThemeButton() {
const { dark, toggle } = useContext(ThemeContext);
return <button onClick={toggle}>Switch to {dark ? "light" : "dark"} mode</button>;
}
function Card() {
const { dark } = useContext(ThemeContext);
const style = {
padding: 16,
borderRadius: 8,
marginTop: 12,
background: dark ? "#1e293b" : "#f1f5f9",
color: dark ? "#f8fafc" : "#0f172a",
};
return <div style={style}>I read the theme from context.</div>;
}
function Layout() {
// Layout does not know about the theme at all.
return (
<div>
<ThemeButton />
<Card />
</div>
);
}
function App() {
return (
<ThemeProvider>
<Layout />
</ThemeProvider>
);
}