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.
| piece | role | analogy |
|---|---|---|
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 Sidebar → Toolbar → ThemedButton with zero props.
// (hit "compile & render" to see Babel's output)
3 · live React (context propagation, proven)
The ThemedButton sits 3 layers deep (ContextApp →
Sidebar → Toolbar → ThemedButton) 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
| intent | pattern | why |
|---|---|---|
| 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 |