← Back to React Course | Chapter 6: Custom Hooks & Advanced Patterns | Lesson 10 of 10

Context Provider Pattern

The Context provider pattern is like a family radio channel — anyone tuned into it can hear the announcement without someone having to run and tell each person individually.

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

markup
<!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

markup
<!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

markup
<!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>
Common Mistakes
  1. 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.
  2. Forgetting to wrap the part of the tree that needs the context in the Provider, so useContext returns the default (often undefined) value.
  3. Putting everything into one giant global context instead of splitting concerns into focused, separate contexts.
Chapter Summary
  • 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.
Browser Support

Available since React 16.3 (stable Context API).

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.