Intermediate⏱️ 9 min📘 Topic 7 of 13

🌐 React Context API — Sharing State Without Prop Drilling

Master the React Context API. Avoid prop drilling, share theme/auth/locale state across the tree, and learn when Context is the wrong tool — with examples.

Prop drilling = passing props through many layers just so a deep child can read them. Tedious and noisy.

Context lets you put a value at the top of the tree and read it anywhere below — no manual passing.

🧪 The 3-step recipe

// 1. Create
const ThemeContext = createContext('light');

// 2. Provide
<ThemeContext.Provider value="dark">
  <App />
</ThemeContext.Provider>

// 3. Consume
const theme = useContext(ThemeContext);

🧠 Good use cases

  • Theme (light/dark)
  • Current user / auth
  • Locale / i18n
  • Feature flags

⚠️ Not for everything

Every consumer re-renders when the context value changes. So:

  • Don't use one giant context for all app state.
  • Do split contexts by concern, or pair with reducers/state libs (Zustand, Jotai, Redux).
  • Do memoize the value object: value={useMemo(() => ({user, login, logout}), [user])}

💻 Code Examples

Auth context with custom hook

const AuthContext = createContext(null);

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const value = useMemo(
    () => ({ user, login: setUser, logout: () => setUser(null) }),
    [user]
  );
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error('useAuth must be inside AuthProvider');
  return ctx;
}
Output:
Any descendant calls `const { user, logout } = useAuth()`.

Theme toggle with context

const ThemeContext = createContext({ theme: 'light', toggle: () => {} });

function App() {
  const [theme, setTheme] = useState('light');
  const toggle = () => setTheme(t => t === 'light' ? 'dark' : 'light');
  return (
    <ThemeContext.Provider value={{ theme, toggle }}>
      <Page />
    </ThemeContext.Provider>
  );
}
Output:
Any nested component can read theme and call toggle().

⚠️ Common Mistakes

  • Creating context value as a fresh object every render — causes ALL consumers to re-render every time.
  • Putting unrelated state into one mega-context — every update re-renders everything.
  • Forgetting to wrap consumers in a Provider — useContext returns the default value, often the wrong type.
  • Reaching for Context too early when a parent component prop would do.

🎯 Interview Questions

Real questions asked at top product and service-based companies.

Q1.What problem does the Context API solve?Beginner
Prop drilling — having to thread the same prop through many layers of components just so a deep descendant can use it. Context lets any descendant read a value placed at an ancestor.
Q2.When should you NOT use Context?Intermediate
When state changes frequently and affects many components (Context re-renders every consumer). Reach for a state lib (Zustand, Redux, Jotai) or split contexts by concern.
Q3.How do you prevent every consumer from re-rendering on every change?Intermediate
Split into multiple smaller contexts, memoize the value object with useMemo, or move the changing state to a dedicated state library with selector subscriptions.
Q4.What does useContext return when there's no Provider above?Intermediate
The default value you passed to createContext(). To enforce that a Provider must exist, wrap useContext in a custom hook that throws if the value is null/undefined.
Q5.Context vs Redux — when to choose which?Advanced
Context is for low-frequency, simple shared data (theme, auth, locale). Redux/Zustand/Jotai are for complex, frequently-changing global state with selectors, middleware and devtools. They aren't competitors — they solve different problems.

🧠 Quick Summary

  • Context = ancestor value readable by any descendant.
  • createContext → Provider → useContext.
  • Great for theme, auth, locale, flags.
  • Bad for hot, frequently-changing app state.
  • Always memoize the provided value object.