Context API — the prop-drilling escape hatch

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Builds on frontend/react: components & props (the prop-drilling escape hatch — context replaces props that pass through layers).

props → context: stop drilling

Passing a prop through components that don't use it is prop-drilling. Context replaces it: a Provider at the top holds a value, and any descendant reads it via useContext — no intermediate props. You call createContext(defaultValue) once; the default is used ONLY when no Provider wraps the consumer. When the Provider's value prop changes, React re-renders EVERY consumer, so memoize the value with useMemo/useCallback.

pieceroleanalogy
createContext(default) creates a context object + Provider + Consumer the broadcast tower — built once, transmits a signal
Provider value=... wraps the tree; sets the current value for all descendants the transmitter — sets what the tower broadcasts
useContext(Ctx) reads the nearest Provider's value (or the default) the receiver — tunes in to the nearest tower
defaultValue fallback when NO Provider is found above the consumer static between stations — what you hear with no tower nearby

1 · the context you write (edit me)

2 · Babel compiles JSX → element tree

<ThemeContext.Provider value={{value}}> becomes React.createElement(ThemeContext.Provider, {{value: value}}, ...). React.useContext(ThemeContext) returns the nearest Provider's value — here it tunnels through SidebarToolbarThemedButton with zero props.

// (hit "compile & render" to see Babel's output)

3 · live React (context propagation, proven)

The ThemedButton sits 3 layers deep (ContextAppSidebarToolbarThemedButton) yet receives theme and toggle with no props passed down. Click the button to toggle; the gold-check does it automatically: assert data-theme="dark" → click → assert "light" → click → assert "dark".

theme: · gold: dark → click → light → click → dark

intent → pattern

intentpatternwhy
declare a context var Ctx = createContext(defaultValue) module scope, once — the default is used only with no Provider
provide a value <Ctx.Provider value={{v}}>...</Ctx.Provider> wraps the subtree; nearest Provider wins for each consumer
read the value var v = useContext(Ctx) subscribes — re-renders when Provider value changes
avoid extra re-renders var value = useMemo(() => ({{a, b}}), [a, b]) new object identity each render re-renders ALL consumers
split concerns separate ThemeContext, UserContext, LocaleContext each Provider is independent — theme change won't re-render user consumers