Topics in this subject
React 6 min read Updated 6 Aug 2026

useContext & Context API

Context shares values without prop drilling; every consumer re-renders on value change—memoize and split.

🧑‍🏫 Sabse pehle — simple mein samjho#

Socho tumhare paas ek value hai (jaise theme "dark") jo bahut saare components ko chahiye. Bina Context ke, tumhe wo value har component ke through prop banake neeche pass karni padegi — isko prop drilling kehte hain (beech waale sab ko unnecessarily pass karna). Context ghar ke WiFi jaisa hai: Provider se ek jagah value do, aur koi bhi child seedha useContext se le le — beech waalon ko touch karne ki zaroorat nahi.

const ThemeContext = createContext("light");   // 1. banao

function App() {
  return (
    <ThemeContext.Provider value="dark">      {/* 2. ek jagah do */}
      <Button />
    </ThemeContext.Provider>
  );
}

function Button() {
  const theme = useContext(ThemeContext);       // 3. seedha lelo, no props!
  return <button className={theme}>Click</button>;
}

Yaad rakho: Context = prop drilling se bachne ka WiFi — ek jagah value do, koi bhi child seedha le le.

Context passes data through the tree without threading props at every level. It solves prop drilling, not global state management (it has no built-in reducers, persistence, or selectors).

The three pieces#

// 1. Create — the argument is the default used ONLY when no Provider is above
const ThemeContext = createContext("light");

// 2. Provide — everything below reads `value`
function App() {
  const [theme, setTheme] = useState("dark");
  return (
    <ThemeContext.Provider value={theme}>
      <Toolbar onToggle={() => setTheme(t => (t === "dark" ? "light" : "dark"))} />
    </ThemeContext.Provider>
  );
}

// 3. Consume — reads the NEAREST Provider's value, subscribes to changes
function Button() {
  const theme = useContext(ThemeContext); // "dark"
  return <button className={theme}>Save</button>;
}

🎯 The defaultValue in createContext is used only when a consumer has no matching Provider ancestor—useful for tests/storybook, a common trap in "why is my value undefined" bugs (you forgot the Provider).

Real-world example: prop drilling in a ride-hailing app 🚕#

Imagine an Ola/Uber-style app shell: App holds the logged-in user, and Header, Sidebar, and Footer all need to show the same user's name/avatar. Without context, user has to be threaded through every intermediate component as a prop — even ones that don't use it themselves.

// ⚠️ Prop drilling — AppShell, RideScreen, TopBar don't use `user` themselves,
// they only pass it through so Header/Sidebar/Footer can read it deeper down.
function App() {
  const [user] = useState({ name: "Tarun", avatar: "🧑" });
  return <AppShell user={user} />;
}

function AppShell({ user }) {
  return (
    <>
      <RideScreen user={user} />
      <Footer user={user} />
    </>
  );
}

function RideScreen({ user }) {
  return (
    <>
      <TopBar user={user} />
      <Sidebar user={user} />
    </>
  );
}

function TopBar({ user }) {
  return <Header user={user} />; // finally consumed 4 levels down
}

function Header({ user }) {
  return <span>{user.avatar} {user.name}</span>;
}

Every intermediate component (AppShell, RideScreen, TopBar) has to accept and forward a user prop it never reads, just so it reaches Header, Sidebar, and Footer. Add a new far-away consumer and you end up touching every layer in between.

// 🟢 Context — Provider once at the top, consumers grab it directly
const UserContext = createContext(null);

function App() {
  const [user] = useState({ name: "Tarun", avatar: "🧑" });
  return (
    <UserContext.Provider value={user}>
      <AppShell />
    </UserContext.Provider>
  );
}

function AppShell() {
  return (
    <>
      <RideScreen />
      <Footer />
    </>
  );
}

function RideScreen() {
  return (
    <>
      <TopBar />
      <Sidebar />
    </>
  );
}

function TopBar() {
  return <Header />;
}

function Header() {
  const user = useContext(UserContext); // straight to the source
  return <span>{user.avatar} {user.name}</span>;
}

function Sidebar() {
  const user = useContext(UserContext);
  return <p>Hi, {user.name}</p>;
}

function Footer() {
  const user = useContext(UserContext);
  return <small>{user.name} is logged in</small>;
}

AppShell, RideScreen, and TopBar no longer mention user at all — they're free to change shape without breaking the data flow to Header, Sidebar, or Footer.

flowchart TD
    subgraph Drilling["⚠️ Prop drilling"]
      A1["App holds user"] -->|"user"| A2["AppShell"]
      A2 -->|"user"| A3["RideScreen"]
      A3 -->|"user"| A4["TopBar"]
      A4 -->|"user"| A5["Header uses user"]
      A3 -->|"user"| A6["Sidebar uses user"]
      A2 -->|"user"| A7["Footer uses user"]
    end
    subgraph ContextFlow["🟢 Context"]
      B1["Provider value=user"] -.->|"useContext"| B2["Header"]
      B1 -.->|"useContext"| B3["Sidebar"]
      B1 -.->|"useContext"| B4["Footer"]
    end

Provider → consumers#

flowchart TD
    P["ThemeContext.Provider value={theme}"] --> A["Layout"]
    A --> B["Toolbar"]
    A --> C["Sidebar"]
    B --> D["Button (useContext)"]
    C --> E["Panel (useContext)"]
    P -.->|"value changes"| D
    P -.->|"value changes"| E

The re-render gotcha 🎯⚠️#

Every consumer re-renders when the context value changes, regardless of React.memomemo compares props, and context is not a prop. Two failure modes:

// ⚠️ New object EVERY render → all consumers re-render every parent render
<UserContext.Provider value={{ user, setUser }}>

// 🟢 Memoize the value so its identity is stable
const value = useMemo(() => ({ user, setUser }), [user]);
<UserContext.Provider value={value}>

🟢 Split contexts by change frequency. If user rarely changes but theme toggles often, one combined context re-renders user consumers on every theme flip. Separate them so each consumer subscribes only to what it needs. A frequent pattern: split state and dispatch into two contexts—dispatch is stable, so action-only components never re-render on state changes.

const StateCtx = createContext(null);
const DispatchCtx = createContext(null);
// components that only dispatch subscribe to DispatchCtx → never re-render on state

React 19 note#

React 19 lets you render <ThemeContext> directly as the provider (the .Provider suffix becomes optional). Both forms are valid in 19; use .Provider for 18 compatibility.

<ThemeContext value={theme}>…</ThemeContext> // React 19

Context vs a state library#

Concern Context API Redux / Zustand / Jotai
Purpose Dependency injection (pass value down) State management + updates
Selective subscription ❌ all consumers re-render ✅ selectors / atoms
Middleware, devtools
Async / thunks Roll your own Built-in patterns
Boilerplate Minimal More (less with Zustand/Jotai)
Best for Theme, locale, auth user, current tenant Large, frequently-updated, cross-cutting state

🟢 Reach for context for low-frequency, tree-wide values (theme, locale, auth). Reach for a store when you need selective subscriptions or high-frequency updates—context has no way to say "only re-render if state.count changed."

Interview Q&A#

Q1. Does React.memo prevent a component from re-rendering when context changes? No. memo only short-circuits prop changes. A useContext consumer always re-renders when its context value's identity changes, even if wrapped in memo.

Q2. Why does wrapping the provider value in an object cause performance issues? An inline object literal is a new reference every render, so the context value's identity changes each time the provider re-renders, forcing all consumers to re-render. Memoize the value with useMemo.

Q3. When would you split one context into several? When different consumers care about different slices that change at different rates. Splitting lets each consumer subscribe only to the slice it uses, avoiding re-renders triggered by unrelated updates (e.g. separate state and dispatch contexts).

Q4. What is the defaultValue argument to createContext actually for? It is returned only when a consumer has no matching Provider above it. In a properly wrapped app it is never used at runtime; it mainly helps isolated tests and Storybook, and documents the shape.

Q5. When should you choose a state library over context? When you need selective subscriptions (re-render only on the slice you use), high-frequency updates, devtools, middleware, or complex async flows—things context does not provide out of the box.