How React propagates values through the component tree without prop drilling.
The Context API lets you share data across the component tree without passing props at every level (prop drilling). A Provider component wraps a subtree and provides a value. Any descendant component can access that value with useContext β and will automatically re-render when the value changes.
React.createContext(defaultValue) creates a Context object with a Provider and Consumer. The defaultValue is used when a component consumes context without a matching Provider above it in the tree.
The Provider component accepts a value prop. All components in the subtree can access this value. You can nest multiple Providers of the same context β the nearest ancestor Provider wins.
useContext(MyContext) returns the current context value from the nearest Provider above the calling component. It subscribes the component to context changes.
When the Provider's value prop changes (by reference), React re-renders ALL components that call useContext(MyContext) in the subtree β regardless of whether their consumed value actually changed.
This is the key efficiency: intermediate components that don't call useContext are not re-rendered. Context skips over them. Only actual consumers get re-rendered.
Creates a context object. Takes a default value used when no Provider is above the consumer in the tree.
Broadcasts a value to all descendant consumers. Nested Providers override outer ones. Value should be memoized to prevent unnecessary re-renders.
Hook that subscribes a component to a context. Re-renders whenever the context value changes.
Context skips straight to consumers β intermediate components don't re-render unless they also call useContext.
React compares context values with Object.is(). Passing a new object literal '{''}' each render triggers all consumers to re-render even if contents are the same.
1const ThemeContext = React.createContext('light'); // default23// Provider β wrap at the top level4function App() {5 const [theme, setTheme] = useState('dark');67 // β οΈ Memoize the value to avoid re-rendering all consumers8 // on every App render, even when theme hasn't changed9 const value = useMemo(() => ({ theme, setTheme }), [theme]);1011 return (12 <ThemeContext.Provider value={value}>13 <Layout />14 </ThemeContext.Provider>15 );16}1718// Consumer β anywhere in the subtree19function Button() {20 const { theme } = useContext(ThemeContext);21 return <button className={theme}>Click</button>;22}
Context solves prop drilling β passing props through many layers of components just to get data to a deeply nested child. But context is best for truly global or widely-shared data (auth, theme, locale). For frequently-changing values, prefer Zustand or Redux to avoid re-rendering every consumer on every change.
Your app has a global theme context providing colors, fonts, and dark/light mode. After adding it, every keystroke in a form input re-renders the entire app because the ThemeProvider is at the root.
The context value is `{ theme, toggleTheme }`. If the ThemeProvider's parent re-renders for ANY reason (new route, state change), a new object is created β all consumers re-render. With 50+ themed components, every parent re-render triggers 50+ unnecessary re-renders.
Split into two contexts: `ThemeValueContext` (read-only, changes only on toggle) and `ThemeDispatchContext` (stable setter function). Components that only READ the theme subscribe to ThemeValueContext. Components that only TOGGLE subscribe to ThemeDispatchContext. Memoize both values with useMemo.
Takeaway: Split contexts by update frequency: separate rarely-changing data (theme colors) from frequently-changing data (user input). This prevents the 'context-triggers-everything' problem and is the pattern used by Redux, Zustand, and Jotai internally.
A settings page passes `locale`, `currency`, and `dateFormat` through 6 levels of nesting: Settings β Layout β Panel β Section β Field β Label. Adding a new preference requires editing 6 components.
Prop drilling creates tight coupling β every intermediate component must know about and forward props it doesn't use. Adding, renaming, or removing a preference requires changing every component in the chain. The intermediate components also re-render when props change even though they don't USE the data.
Create a `PreferencesContext` and consume it directly in Label with `useContext(PreferencesContext)`. Intermediate components (Layout, Panel, Section, Field) don't receive or forward preference props. The preferences data 'teleports' from Provider to consumer, skipping all intermediate components.
Takeaway: Context eliminates prop drilling for cross-cutting concerns (theme, auth, locale, feature flags). Use it when data needs to be available deep in the tree and passing it through 3+ levels of components that don't use it. For frequently-updating state, consider a state manager instead.