React useContext Hook

Broadcast global state across component subtrees and eliminate prop drilling using createContext and useContext.

TL;DR

  1. Share state across deep component trees without tedious intermediate prop drilling.
  2. Create typed context objects using the createContext API factory function.
  3. Consume active context values reliably with the declarative useContext hook.

Context Creation and Providing

    Create Typed Context

    Instantiate context with initial fallback value.

    import { createContext } from 'react';
    
    interface ThemeCtx {
      theme: 'light' | 'dark';
      toggle: () => void;
    }
    
    export const ThemeContext =
      createContext<ThemeCtx | null>(null);
    Context Provider Wrapper

    Supply value to all descendants in the component subtree.

    export function ThemeProvider({
      children,
    }: { children: React.ReactNode }) {
      const [theme, setTheme] = useState<'light' | 'dark'>(
        'light'
      );
      const toggle = () => {
        setTheme((t) =>
          t === 'light' ? 'dark' : 'light'
        );
      };
      return (
        <ThemeContext.Provider value={{ theme, toggle }}>
          {children}
        </ThemeContext.Provider>
      );
    }

Consuming Context

    Direct Hook Consumption

    Read context value inside any child component.

    export function ThemeButton() {
      const ctx = useContext(ThemeContext);
      if (!ctx) throw new Error('Missing Provider');
      return (
        <Button onClick={ctx.toggle}>
          {ctx.theme}
        </Button>
      );
    }
    Custom Consumer Hook

    Encapsulate null check inside dedicated custom hook.

    export function useTheme() {
      const ctx = useContext(ThemeContext);
      if (!ctx) {
        throw new Error('useTheme outside Provider');
      }
      return ctx;
    }
    Default Context Fallback

    Provide sensible default to avoid null assertions.

    const LangContext = createContext<string>('en');

Multiple and Nested Contexts

    Nesting Providers

    Stack multiple context providers cleanly at root.

    return (
      <AuthProvider>
        <ThemeProvider>
          <NotificationProvider>
            <AppRouter />
          </NotificationProvider>
        </ThemeProvider>
      </AuthProvider>
    );
    Scoped Subtree Override

    Override context value for a specific isolated section.

    const override = { theme: 'dark', toggle };
    return (
      <ThemeContext.Provider value={override}>
        <DarkSection />
      </ThemeContext.Provider>
    );
    Separating State and Dispatch

    Split state and dispatch into two contexts to save renders.

    const StateCtx = createContext<State>(initialState);
    const DispatchCtx = createContext<Dispatch>(() => {});

Context Best Practices

    Avoid Prop Drilling

    Eliminate passing props across intermediate layout nodes.

    // Cleaner architecture with context
    function DeepChild() {
      const user = useUser();
      return <Text>{user.name}</Text>;
    }
    Prefer Composition First

    Pass components as children before creating new context.

    // Often better than context
    <Page layout={<Sidebar user={user} />} />
    Memoize Context Value

    Prevent unnecessary consumer re-renders on parent updates.

    const val = useMemo(
      () => ({ theme, toggle }),
      [theme]
    );
    return <ThemeContext.Provider value={val} />;

Tips

  1. Always create a custom hook like useTheme() that wraps useContext and throws an error if called outside its Provider.
  2. Keep context values scoped tightly to subtrees that actually require them rather than wrapping the entire application tree unnecessarily.

Warnings

  1. Every component consuming a context will re-render whenever the Provider value prop changes reference.
  2. Do not reach for Context as a substitute for simple component composition using the children prop.

In Practice

FAQ