Context provides data to any descendant component without passing props through every level — it solves prop drilling but is not state management, and context value changes re-render all consumers.
Provides data to any descendant without passing through intermediate components — createContext, Provider, useContext.
Context is a data distribution mechanism, not a state manager. You still need useState/useReducer for the actual state logic.
When a Provider's value changes, ALL consumers re-render — even if they only use a part of the context that didn't change.
Ideal for theme, locale, auth status. Not suited for frequently changing data like typing input or real-time updates.
Split contexts by change frequency, memoize Provider values with useMemo, and wrap consumers with React.memo.
The Context API lets you pass data through the component tree without manually threading props through every intermediate component. It's React's built-in dependency injection mechanism.
Three steps: create, provide, consume.
const ThemeContext = createContext('light') — creates a context with a default value.<ThemeContext.Provider value={theme}> — wraps a subtree and provides the value to all descendants.const theme = useContext(ThemeContext) — any descendant reads the nearest Provider's value.If no Provider exists above the consumer, useContext returns the default value from createContext.
Context is ideal for data that many components at different nesting levels need, but that changes infrequently:
Context is not optimized for frequently changing data. When a Provider's value changes, every component that calls useContext for that context re-renders — even if the specific piece of data they use hasn't changed. For rapidly changing data (typing input, scroll position, real-time updates), use state management libraries (Zustand, Redux) that offer fine-grained subscriptions.
A common misconception is that Context replaces Redux or Zustand. Context is a data distribution mechanism — it doesn't manage state, handle updates, or provide optimized subscriptions. You still need useState or useReducer to manage the actual state; Context just makes it available without prop drilling. The combination of useReducer + Context can work for simple global state, but it lacks the performance optimizations of dedicated state management libraries.
Since all consumers re-render when context value changes:
ThemeContext and UserContext instead of one AppContext.useMemo: <Ctx.Provider value={useMemo(() => ({ theme, toggle }), [theme])}>. This prevents new object references from triggering unnecessary consumer re-renders.React.memo so they only re-render when their props (including context-derived values) actually change.React 19: use() with Context
React 19 introduces the use() hook, which can accept a context: const theme = use(ThemeContext). Unlike useContext, use() can be called conditionally and works inside try/catch blocks and after early returns.
Context solves prop drilling — it's dependency injection, not state management. All consumers re-render on any context value change, making it unsuitable for frequently changing data. Split contexts and memoize values for performance. For complex state with frequent updates, use dedicated state management libraries instead.
Fun Fact
The original Context API (React 16.3) replaced an unstable, undocumented context mechanism that had existed since React's early days. The old API was so unreliable that the React team actively warned against using it — but Redux depended on it internally, which is one reason Redux existed in the first place.