Topic 09 / 14

Context — Sharing State Without Prop Drilling

~10 min read  //  React Series  //  Coding India

The Problem: Prop Drilling

The current user is needed by a button five levels deep. Passing it through four components that don’t care is prop drilling — noisy and brittle. Context lets a parent provide a value and any descendant consume it directly.

The Three Steps

import { createContext, useContext, useState } from "react";

// 1. create
const ThemeContext = createContext(null);

// 2. provide — wrap a subtree, pass the value
function App() {
  const [theme, setTheme] = useState("dark");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Layout />
    </ThemeContext.Provider>
  );
}

// 3. consume — anywhere below, no props needed
function ThemeToggle() {
  const { theme, setTheme } = useContext(ThemeContext);
  return (
    <button onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
      {theme === "dark" ? "☀️" : "🌙"}
    </button>
  );
}

When the provider’s value changes, every consuming component re-renders.

The Production Pattern: Provider + Hook Module

Real apps wrap each context in its own file with a custom hook — clean API, good errors:

// AuthContext.jsx
const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  async function login(email, password) {
    const u = await api("/login", { method: "POST", body: { email, password } });
    setUser(u);
  }
  function logout() { setUser(null); }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth must be used inside <AuthProvider>");
  return ctx;
}
// anywhere in the app:
const { user, logout } = useAuth();

{user ? <button onClick={logout}>Log out</button> : <LoginLink />}

Providers nest at the root: <AuthProvider><ThemeProvider><App />…

When Context Is Right — and Wrong

Right: truly app-wide, slowly-changing data — current user, theme, language, feature flags.

Wrong:

  • Passing props down one or two levels — just pass props; drilling a little is fine and explicit.
  • Fast-changing data (every keystroke, mouse position) — every consumer re-renders on every change.
  • A grab-bag “AppContext” holding everything — split contexts by concern so consumers only re-render for data they use.

For complex client state beyond context’s comfort zone, the community reaches for Zustand or Redux Toolkit; for server data, TanStack Query. Learn context first — the others build on the same provider/consumer mental model.