Skip to the solution
MediumFrontend6 min read

What is the Context API in React and when would you use it?

913 views

The problem

Question presented to candidate: "What is the Context API, and how do you decide whether a value belongs in it?"

What a strong answer should cover:

  • createContext makes a context; a provider supplies a value; any descendant reads it with useContext.
  • It solves prop drilling — delivering a value without threading it through intermediate components.
  • It is a transport mechanism, not a state manager. It does not store or update anything; the state still lives in a component or a store, and Context merely carries it.
  • The cost: every consumer re-renders when the provider value changes, and memo does not stop that.
  • So it suits values that are read widely and change rarely: theme, locale, current user, feature flags, a stable dispatch.
  • It suits badly: anything changing frequently, like form input or cursor position.
  • The default value is only used when a consumer has no provider above it — useful for tests and for catching missing providers.
  • Convention: wrap the useContext call in a custom hook that throws when the provider is missing.
  • Multiple small contexts beat one large object, because the granularity of re-rendering follows the granularity of the contexts.

Clarifying questions expected:

  • "How often does this value change, and how many components read it?"
  • "Have we tried composition first?" — often it removes the need entirely.

Code / implementation expected: Yes — a provider, a custom consumer hook with a missing-provider guard, and a memoised value.

  • state management
  • context api
  • props
  • global state

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read · 9 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactA provider with a memoised value, and a guarded consumer hook
import { createContext, useContext, useState, useMemo, useCallback, memo } from "react";

// null as the default so a missing provider is detectable rather than silently
// giving every consumer a plausible-looking fallback.
const ThemeContext = createContext(null);

// The custom hook keeps the context object private and turns "rendered outside
// the provider" into a clear error instead of a confusing undefined.
function useTheme() {
  const ctx = useContext(ThemeContext);
  if (ctx === null) throw new Error("useTheme must be used inside a ThemeProvider");
  return ctx;
}

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");
  const toggle = useCallback(() => setTheme((t) => (t === "light" ? "dark" : "light")), []);

  // Without useMemo this object is new on every render, so every consumer
  // re-renders even when the theme has not changed.
  const value = useMemo(() => ({ theme, toggle }), [theme, toggle]);

  return <ThemeContext value={value}>{children}</ThemeContext>;
}

let panelRenders = 0;
const ThemedPanel = memo(function ThemedPanel() {
  panelRenders++;
  const { theme, toggle } = useTheme();
  return (
    <div style={{
      padding: 12, borderRadius: 8,
      background: theme === "dark" ? "#222" : "#f4f4f4",
      color: theme === "dark" ? "#eee" : "#222",
    }}>
      <p style={{ margin: "0 0 8px" }}>
        theme: <strong>{theme}</strong> · panel renders: <strong>{panelRenders}</strong>
      </p>
      <button onClick={toggle}>toggle theme</button>
    </div>
  );
});

// Deliberately rendered OUTSIDE the provider to show the guard firing.
function Unguarded() {
  try {
    useTheme();
    return <p>read the theme</p>;
  } catch (e) {
    return <p style={{ color: "crimson", fontSize: 13 }}>caught: {e.message}</p>;
  }
}

export default function App() {
  const [tick, setTick] = useState(0);

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6, maxWidth: 420 }}>
      <ThemeProvider>
        <ThemedPanel />
      </ThemeProvider>

      <p style={{ marginTop: 12 }}>
        <button onClick={() => setTick((t) => t + 1)}>
          re-render the app ({tick})
        </button>
      </p>

      <Unguarded />

      <p style={{ color: "#666", fontSize: 13 }}>
        Press the app re-render button: the panel counter stays put, because the
        memoised value keeps the same identity. Toggle the theme and it moves.
        The last line shows the custom hook catching a missing provider.
      </p>
    </div>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 42 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextMedium

How does React's Batching work?

Continue