Chapter 2 of 4

Creating and Using Context

The three pieces: create, provide, consume.

A context is created once at module level, provided somewhere near the top of the tree, and read anywhere below.

// theme-context.js
import { createContext, useContext } from "react";

const ThemeContext = createContext("light");

export function ThemeProvider({ value, children }) {
  return (
    <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}
// App.jsx
<ThemeProvider value={theme}>
  <Layout />
</ThemeProvider>

// Any component below, at any depth
function NavButton() {
  const theme = useTheme();
  return <button className={theme}>Menu</button>;
}

The default value

The argument to createContext is used only when a component reads the context with no matching provider above it. It is a fallback, not an initial value.

const UserContext = createContext(null);

export function useUser() {
  const value = useContext(UserContext);
  if (value === null) {
    throw new Error("useUser must be used inside a UserProvider");
  }
  return value;
}

Wrapping the provider in a hook

Exporting a useTheme hook rather than the raw context gives you one place to add validation, defaults or logging later, and stops the context object leaking across your codebase.

Nested providers

A component reads the nearest provider above it, so nesting lets a subtree override the value.

<ThemeProvider value="light">
  <Page />                        {/* light */}
  <ThemeProvider value="dark">
    <Sidebar />                   {/* dark */}
  </ThemeProvider>
</ThemeProvider>