React Hooks Part 5: Sharing State with useContext
useContext lets any component read shared state without threading props through every layer. This article covers createContext, Provider setup, and the patterns that keep context from becoming a performance trap.
React Hooks: A Complete Guide
A four-part deep-dive into React Hooks — from managing state with useState to squeezing performance out of useCallback and useMemo. Each article is standalone yet builds on the previous, giving you both quick reference and progressive mastery.
- 3 React Hooks Part 3: useRef and useReducer
- 4 React Hooks Part 4: useCallback, useMemo, and Custom Hooks
- 5 React Hooks Part 5: Sharing State with useContext
- 6 React Hooks Part 6: useId, useTransition, and useDeferredValue
- 7 React Hooks Part 7: useImperativeHandle and useLayoutEffect
Table of Contents
The prop drilling problem
Imagine a theme setting that five levels of nested components all need. Without context you'd pass it as a prop through every layer — even the ones that don't use it. That's prop drilling, and it makes refactoring painful.
useContext solves this by letting any component in the tree read a value directly from the nearest matching Provider above it.
Creating and providing a context
// ThemeContext.js
import { createContext } from 'react'
export const ThemeContext = createContext('light') // 'light' is the default
Wrap the parts of your tree that need access inside a Provider:
import { useState } from 'react'
import { ThemeContext } from './ThemeContext'
function App() {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Layout />
</ThemeContext.Provider>
)
}
Any component inside <Layout /> — no matter how deeply nested — can read theme and setTheme.
Consuming with useContext
import { useContext } from 'react'
import { ThemeContext } from './ThemeContext'
function Header() {
const { theme, setTheme } = useContext(ThemeContext)
return (
<header data-theme={theme}>
<button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
Toggle theme
</button>
</header>
)
}
No props needed. The component reaches up to the nearest ThemeContext.Provider.
The pattern: context + custom hook
Wrap useContext in a custom hook so consumers don't import the raw context object:
// useTheme.js
import { useContext } from 'react'
import { ThemeContext } from './ThemeContext'
export function useTheme() {
const ctx = useContext(ThemeContext)
if (!ctx) throw new Error('useTheme must be used inside ThemeProvider')
return ctx
}
// Usage — clean and self-documenting
const { theme, setTheme } = useTheme()
This also gives you a clear error if someone accidentally uses the hook outside the Provider.
Performance: context re-renders every consumer
When the context value changes, every component that calls useContext re-renders. For a large tree this matters.
Split contexts by update frequency
// Don't put fast-changing state and stable config in the same context
const UserContext = createContext(null) // changes rarely
const CartContext = createContext(null) // changes often
// Fast-changing consumers only re-render when CartContext changes
Memoize the value object
const value = useMemo(() => ({ theme, setTheme }), [theme])
return <ThemeContext.Provider value={value}>…</ThemeContext.Provider>
Without useMemo, the value object is a new reference every render, causing all consumers to re-render even when theme hasn't changed.
Context vs state management libraries
Context isn't a Redux replacement for all use cases. The rule of thumb:
| Use case | Tool |
|---|---|
| Theme, locale, user session | Context |
| Frequently updated global state | Zustand / Jotai / Redux |
| Server data, caching | React Query / SWR |
Context is perfect for "ambient" data that changes infrequently and needs to be read everywhere.
Key takeaways
createContext→Provider→useContext: the three-step pattern.- Wrap
useContextin a custom hook for cleaner APIs and better error messages. - Split contexts by update frequency to avoid unnecessary re-renders.
- Memoize the Provider's value object when it contains objects or functions.