How does React Context and useContext work?

React Context lets you pass data through the component tree without prop drilling. Create a context, wrap with a Provider, and consume with useContext.

React

Overview

Context solves "prop drilling" - passing props through many levels of components that don't need them, just to get data to a deeply nested child. With Context, you create a value at a high level and any descendant can read it directly, regardless of nesting depth.

Key Points

  • •`createContext(defaultValue)` creates the context object.
  • •`<Context.Provider value={...}>` makes a value available to all descendants.
  • •`useContext(Context)` subscribes a component to the context value.
  • •When the Provider's `value` changes, all `useContext` consumers re-render.
  • •Context is NOT a replacement for a state manager - it is a delivery mechanism, not a store.
  • •Split contexts by concern (ThemeContext, AuthContext) to avoid unnecessary re-renders.

Examples

Theme context end-to-end

tsx
import { createContext, useContext, useState } from 'react';

type Theme = 'light' | 'dark';
const ThemeContext = createContext<Theme>('light'); // default value

// Provider - wrap the part of the tree that needs access
export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light');
  return (
    <ThemeContext.Provider value={theme}>
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        Toggle
      </button>
      {children}
    </ThemeContext.Provider>
  );
}

// Consumer - any descendant, no matter how deep
function Card() {
  const theme = useContext(ThemeContext);
  return <div className={`card card--${theme}`}>...</div>;
}

Auth context with actions

tsx
interface AuthContextValue {
  user:   User | null;
  login:  (credentials: Credentials) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextValue | null>(null);

// Convenience hook with built-in null check
export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be used inside <AuthProvider>');
  return ctx;
}

// Usage in any descendant
function UserMenu() {
  const { user, logout } = useAuth();
  return <button onClick={logout}>Log out {user?.name}</button>;
}

Pro Tip

Do not reach for Context for every piece of shared state. Start with props; lift state when siblings need it; use Context only when prop drilling spans 3+ levels or when state is genuinely global (current user, theme, locale). For complex state logic, combine Context with `useReducer`.