Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 154
Intermediate

React useContext Hook

Share state across React components without prop drilling using the useContext hook.

TL;DR

  1. 01Create a Context with createContext for shared data.
  2. 02Wrap components with a Provider to make data available.
  3. 03Use useContext to access data in any child component.

Tips

  1. 01Create custom hooks for contexts to simplify usage and add error checking for consumers.

Warnings

  1. 01Every context change causes all consumers to re-render — split contexts by concern to avoid performance issues.
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 155
Intermediate

React useContext Hook

(continued)

Creating Context

  • Create a context to hold shared data.
    const ThemeContext = React.createContext();
  • Provide an initial value as a fallback when no provider wraps the consumer.
    const UserContext = React.createContext({
      user: null,
      setUser: () => {}
    });
  • Export the context for use in other components.
    export const ThemeContext = React.createContext();
  • Keep context creation in its own file for clarity.
    // theme-context.js
    import { createContext } from "react";
    export const ThemeContext = createContext("light");
  • Use TypeScript generics to type the context value.
    interface User { id: number; name: string; }
    interface UserContextType { user: User | null; setUser: (u: User) => void; }
    
    const UserContext = createContext<UserContextType | undefined>(undefined);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 156
Intermediate

React useContext Hook

(continued)

Creating a Provider

  • Wrap your app with a Provider component.
    function App() {
      const [theme, setTheme] = useState("light");
      
      return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
          <Header />
          <Main />
          <Footer />
        </ThemeContext.Provider>
      );
    }
  • Provider passes value to all child components.
    function Layout({ children }) {
      const [user, setUser] = useState(null);
      
      return (
        <UserContext.Provider value={{ user, setUser }}>
          {children}
        </UserContext.Provider>
      );
    }
  • Extract the provider into its own component to keep App clean.
    export function ThemeProvider({ children }) {
      const [theme, setTheme] = useState("light");
      return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
          {children}
        </ThemeContext.Provider>
      );
    }
  • Memoize the context value to avoid re-rendering every consumer.
    function UserProvider({ children }) {
      const [user, setUser] = useState(null);
      const value = useMemo(() => ({ user, setUser }), [user]);
      return (
        <UserContext.Provider value={value}>
          {children}
        </UserContext.Provider>
      );
    }
  • Nest multiple providers at the top of the tree.
    function App() {
      return (
        <AuthProvider>
          <ThemeProvider>
            <Router>
              <AppRoutes />
            </Router>
          </ThemeProvider>
        </AuthProvider>
      );
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 157
Intermediate

React useContext Hook

(continued)

Consuming Context

  • Access context with useContext hook.
    function Header() {
      const { theme, setTheme } = useContext(ThemeContext);
      
      return (
        <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
          Current theme: {theme}
        </button>
      );
    }
  • useContext works in any descendant component, no matter how deep.
    function DeepComponent() {
      const { user } = useContext(UserContext);
      return <p>User: {user?.name}</p>;
    }
  • Returns the createContext default value if no matching Provider is above it, or undefined if no default was set.
  • Destructure only the values you need to be explicit.
    // Clear: names the exact values this component uses
    const { theme } = useContext(ThemeContext);
  • Re-renders happen whenever the context value changes.
    // This component re-renders every time theme or setTheme changes
    const { theme, setTheme } = useContext(ThemeContext);
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 158
Intermediate

React useContext Hook

(continued)

Custom Hooks

  • Create a custom hook to simplify context access.
    function useTheme() {
      const context = useContext(ThemeContext);
      if (!context) {
        throw new Error("useTheme must be inside ThemeProvider");
      }
      return context;
    }
    
    function Header() {
      const { theme, setTheme } = useTheme();
      return <button onClick={() => setTheme("dark")}>{theme}</button>;
    }
  • Custom hooks provide error checking and a cleaner API.
  • Makes consuming context easier for other developers.
  • Add computed values or helpers inside the custom hook.
    function useAuth() {
      const { user } = useContext(AuthContext);
      return {
        user,
        isLoggedIn: !!user,
        isAdmin: user?.role === "admin"
      };
    }
  • Export only the custom hook, not the raw context.
    // users import useUser, not UserContext directly
    export { useUser };       // export hook
    // don't export UserContext — keep it private
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 159
Intermediate

React useContext Hook

(continued)

Best Practices

  • Place the Provider as close as possible to the components that need it, not always at the app root.
    // Scope SidebarContext to the sidebar, not the whole app
    function Sidebar() {
      return (
        <SidebarContext.Provider value={collapsed}>
          <SidebarPanel />
        </SidebarContext.Provider>
      );
    }
  • Colocate context with the feature that owns it instead of lifting everything to a shared global file — move it up only when a second, unrelated feature genuinely needs the same data.
    // features/cart/cart-context.jsx — lives next to the feature, not in a top-level contexts/ folder
    export const CartContext = createContext(null);
  • Avoid context for state that changes on every keystroke or frame, like form input or animation values — high-frequency updates re-render every consumer and are a poor fit for the context model.
    // Fast-changing values fit local state or a state library better than context
    const [query, setQuery] = useState(""); // keep local, not in context
  • Prefer local state or props for data used by only 1–2 components.
    // Don't reach for Context just to avoid one prop
    // Context is useful for truly global data: auth, theme, locale
  • Test context consumers by wrapping them in the provider, not by mocking the context module.
    test("shows username from context", () => {
      render(
        <UserContext.Provider value={{ user: { name: "Alice" }, setUser: jest.fn() }}>
          <UserDisplay />
        </UserContext.Provider>
      );
      expect(screen.getByText("Alice")).toBeInTheDocument();
    });
  • Build a small reusable test helper that wraps render with your real providers, so every test exercises the same provider tree your app uses in production.
    function renderWithProviders(ui) {
      return render(<AuthProvider><ThemeProvider>{ui}</ThemeProvider></AuthProvider>);
    }
Notes
Useful Cheatsheetsusefulcheatsheets.com
React useContext Hook
Chapter 22 · Page 160
Intermediate

React useContext Hook

(FAQ)

FAQ

Use useContext when data needs to be accessed by many components at different nesting levels, such as theme, locale, or auth state. Prop drilling becomes a maintenance burden once you're passing props through more than 2-3 layers of components that don't use the data themselves.

All consumers re-render whenever any value in the context object changes. Split your context into smaller, focused contexts by concern (e.g., separate AuthContext from ThemeContext) so that a change in one doesn't trigger re-renders in unrelated consumers.

Include an updater function in your context value alongside the data — pass a useState setter or a dispatch function from useReducer. Consumers can then call that function to trigger state changes that propagate back through the Provider.

useContext is built-in and ideal for low-frequency updates like theme or auth, with no extra dependencies. Redux adds middleware, devtools, and a strict unidirectional flow suited to complex, high-frequency global state — useContext alone can become hard to scale when many unrelated pieces of state share a single context.

In your custom hook, check that the returned context value is not undefined and throw a descriptive error if it is — for example: if (!ctx) throw new Error('useAuth must be used within an AuthProvider'). This gives a clear error instead of a silent undefined crash deep in the component tree.