Context Provider Pattern
In this page:
Creating and Providing Context
React.createContext() creates a Context object with a default value. Wrapping part of the component tree in <MyContext.Provider value={...}> makes that value available to every component nested inside it, no matter how deep, without threading it through props manually.
Note: Give the context a sensible default value (the second argument isn't required, but createContext() also accepts one) so components used outside a provider don't crash unexpectedly.
Warning: Only components rendered INSIDE the Provider can read its value — a component outside the Provider's JSX tree falls back to the context's default value.
Example: Creating and Providing Context
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const ThemeContext = React.createContext("light");
function Display() {
const theme = React.useContext(ThemeContext);
return <p>Current theme: {theme}</p>;
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Display />
</ThemeContext.Provider>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Wrapping Provider + State Together
A common pattern is building a dedicated Provider component that holds the state itself and passes it (plus a setter) down through context, rather than sprinkling useState calls across the app. This keeps all the related state and update logic in one place.
Note: Name this component something like ThemeProvider, and export both it and the raw ThemeContext (or a custom hook) from the same file.
Warning: If ThemeProvider itself isn't rendered near the top of the app, only components below it in the tree will see the shared state.
Example: Wrapping Provider + State Together
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const ThemeContext = React.createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState("light");
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
}
function ThemeToggle() {
const { theme, setTheme } = React.useContext(ThemeContext);
return <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>Theme: {theme}</button>;
}
function App() {
return <ThemeProvider><ThemeToggle /></ThemeProvider>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Wrapping useContext in a Custom Hook
Instead of every consumer calling useContext(ThemeContext) directly, it's common to expose a small custom hook like useTheme() that does this internally and can also throw a helpful error if used outside the provider. This makes misuse easy to catch early.
Note: Throwing a clear error inside the custom hook ('useTheme must be used within a ThemeProvider') saves a lot of debugging time versus a silent undefined value.
Warning: Without this guard, forgetting the provider produces a confusing 'Cannot read properties of undefined' error deep inside a component instead of a clear message.
Example: Wrapping useContext in a Custom Hook
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const ThemeContext = React.createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = React.useState("light");
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
}
function useTheme() {
const ctx = React.useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used within a ThemeProvider");
return ctx;
}
function ThemeLabel() {
return <p>Theme is: {useTheme().theme}</p>;
}
function App() {
return <ThemeProvider><ThemeLabel /></ThemeProvider>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Passing a brand-new object literal as the Context value on every render, causing every consumer to re-render even when the actual data didn't change.
- Forgetting to wrap the part of the tree that needs the context in the Provider, so useContext returns the default (often undefined) value.
- Putting everything into one giant global context instead of splitting concerns into focused, separate contexts.
- The provider pattern wraps a piece of the component tree in a Context.Provider to make a value available to all its descendants.
- Consumers read the value with useContext, without needing it passed down manually through every level of props.
- Wrapping the provider's value in useMemo avoids creating a new object on every render, preventing unnecessary consumer re-renders.
- It's common to pair a provider with a custom hook (like useTheme()) that wraps useContext for a cleaner consumer API.
Available since React 16.3 (stable Context API).
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: