Q&A
How to use the useContext hook in React
Overview
Context solves the "prop drilling" problem: passing data through many intermediary components just to reach a deep child. With context, you publish data at a high level and any descendant can read it directly. It's the go-to solution for theme, authentication, locale, and other app-wide state - though for frequently-changing state, external stores like Zustand may be better.
graph TD
A["createContext(default)"] --> B["<Context.Provider value={...}>"]
B --> C["Any descendant calls useContext(Context)"]
C --> D["Gets the current value - no prop drilling"]
B --> E["Value changes → all consumers re-render"]
style C fill:#1e1b4b,color:#fff
Step 1: Create the Context
// ThemeContext.js
import { createContext } from "react";
export const ThemeContext = createContext("light"); // "light" = default value
// The default is used ONLY if a component renders outside any Provider
Step 2: Provide a Value
Wrap the subtree that needs access in a Provider and pass the current value:
// App.js
import { useState } from "react";
import { ThemeContext } from "./ThemeContext";
import Toolbar from "./Toolbar";
function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
Any component inside <ThemeContext.Provider> can now read { theme, setTheme }.
Step 3: Consume with useContext
// ThemedButton.js - deeply nested, no props needed
import { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
style={{ background: theme === "dark" ? "#333" : "#fff" }}
onClick={() => setTheme(t => t === "dark" ? "light" : "dark")}
>
Toggle ({theme})
</button>
);
}
useContext returns whatever the nearest Provider's value is. When that value changes, ThemedButton re-renders.
Custom Hook Pattern (best practice)
Wrap useContext in a custom hook to hide the context detail and add error checking:
// useTheme.js
import { useContext } from "react";
import { ThemeContext } from "./ThemeContext";
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) throw new Error("useTheme must be used within a ThemeProvider");
return context;
}
// Usage anywhere:
const { theme, setTheme } = useTheme();
This is the idiomatic pattern - consumers never import ThemeContext directly, only useTheme.
Multiple Contexts
You can nest as many providers as needed:
<AuthContext.Provider value={authState}>
<ThemeContext.Provider value={themeState}>
<LocaleContext.Provider value={locale}>
<App />
</LocaleContext.Provider>
</ThemeContext.Provider>
</AuthContext.Provider>
Each component calls useContext only for the context(s) it actually needs.
Performance Note: Re-renders on Every Value Change
Every consumer of a context re-renders whenever the Provider's value changes. If you pass an object/array directly as value, a new reference is created on every parent render, causing all consumers to re-render unnecessarily. Fix: useMemo the value, or split frequent-change and rare-change data into separate contexts.
// Memoize to prevent unnecessary re-renders:
const value = useMemo(() => ({ theme, setTheme }), [theme]);
<ThemeContext.Provider value={value}>
Context vs Global State Libraries
Context is ideal for low-frequency, app-wide values (auth user, theme, locale). For high-frequency state (many updates per second, large data trees) or complex update logic, external stores (Zustand, Redux, Jotai) are more efficient - they avoid re-rendering every consumer on every change.
Why This Matters in Interviews
React interviews use useContext to probe context mechanics and the prop-drilling solution. Key points: createContext → Provider → useContext chain; re-renders on value change; wrapping in a custom hook (the standard pattern); the re-render performance caveat with object values and useMemo as the fix; and when not to use context (high-frequency updates → global state library). Showing the custom-hook wrapper pattern signals idiomatic React.
Common Pitfalls
- A component consuming context outside any Provider - gets the
createContextdefault value silently (hard to debug). - Passing a new object/array as
valueon every render, causing all consumers to re-render unnecessarily. - Using context for complex state logic that's better handled by
useReducer+ context or an external store. - Putting too much data in one context - split into separate, focused contexts.
References
- React
useContext: https://react.dev/reference/react/useContext - React Context: https://react.dev/learn/passing-data-deeply-with-context