useContext & context
Share values like theme and the current user without prop drilling, and avoid unnecessary re-renders.
Props are the main way to pass data in React — explicit and easy to follow. But some data is needed by many components at different depths: the current user, the theme, the language, a shopping cart. Passing it through every intermediate component ("prop drilling") gets tedious and fragile. Context lets a parent make a value available to its entire subtree.
The problem: prop drilling#
Only ThemedButton needs theme, but every component in between must accept and forward it.
Using context in three steps#
1. Create a context
2. Provide a value
Wrap part of your tree with the context. In React 19 you render the context itself as the provider:
(Before React 19 this was written <ThemeContext.Provider value={theme}>, which still works.)
3. Read it anywhere below
useContext finds the nearest provider above the component and returns its value. When that value changes, every component reading it re-renders automatically.
Updating context from below#
Put the state and the functions to change it into the context value:
This provider component + custom hook pattern is how most apps use context:
- The provider owns the state and hides the details.
- The
useAuthhook gives a clean API and a helpful error if someone forgets the provider (instead of a confusingnull). useMemokeeps the value object stable so consumers only re-render whenuseractually changes.
Default values#
The argument to createContext is used only when there's no provider above. It's useful for sensible fallbacks ("light") and for tests. For contexts that must always be provided, use null and throw from the custom hook as above.
Nesting and overriding#
The nearest provider wins, so you can override a value for part of the tree:
Context and performance#
Every component that calls useContext(SomeContext) re-renders when the context value changes. Keep this efficient:
- Memoise object/array values with
useMemo(as above), so a provider's parent re-rendering doesn't create a "new" value. - Split contexts by how often they change. A rarely-changing
ThemeContextshouldn't be combined with a fast-changingCursorPositionContext. - Separate state and actions: putting
dispatch(which never changes) in its own context means components that only send updates never re-render because of them.
(useReducer is the next lesson.) If you find yourself fighting context re-renders for frequently-changing global state, a store library like Zustand may fit better — see State management.
Reading context with use#
React 19's use API can also read context, and unlike useContext it may be called inside conditions:
When to use context#
Good fits:
- Theme, locale/language, current user/session, feature flags
- Values used by a group of components in one area (a form's state, a tabs component's active tab)
- Dependencies like an API client or a router
Before reaching for context, consider:
- Passing props — a few levels of explicit props is fine and easy to trace.
- Composition — pass components as
childrenso intermediate components don't need the data at all:
Common mistakes#
- Using
useContextin a component that isn't inside the provider and getting the default value (oftennull). - Creating a new value object on every render without
useMemo. - Putting everything into one giant "AppContext".
- Reaching for context when two levels of props would do.
What's next#
Next: useReducer — a structured way to manage complex state, which pairs beautifully with context.
Check your understanding
Quick quiz
1.What problem does context solve?
2.What does
useContext(ThemeContext)return when there is no matching provider above the component?3.A provider renders
<UserContext value={{ user, setUser }}>. Why might every consumer re-render when the provider's parent re-renders?
Finished reading?
Mark this lesson complete to track your progress.