React Context
Full Reference: See advanced.md for context selectors with useSyncExternalStore, dependency injection, React 19 use(), testing context, and TypeScript patterns.
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: react topic: context for comprehensive documentation.
Basic Usage
import { createContext, useContext, ReactNode } from 'react';
// 1. Create context with default value
interface ThemeContextValue {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextValue | null>(null);
// 2. Create Provider component
function ThemeProvider({ children }: { children: ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
}, []);
const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// 3. Create custom hook for consuming
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// 4. Use in components
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header className={theme}>
<button
Switch to {theme === 'light' ? 'dark' : 'light'}
</button>
</header>
);
}
// 5. Wrap app with provider
function App() {
return (
<ThemeProvider>
<Header />
<Main />
</ThemeProvider>
);
}
Context with Reducer
For complex state management:
interface AuthState {
user: User | null;
isLoading: boolean;
error: string | null;
}
type AuthAction =
| { type: 'LOGIN_START' }
| { type: 'LOGIN_SUCCESS'; payload: User }
| { type: 'LOGIN_ERROR'; payload: string }
| { type: 'LOGOUT' };
const initialState: AuthState = {
user: null,
isLoading: false,
error: null,
};
function authReducer(state: AuthState, action: AuthAction): AuthState {
switch (action.type) {
case 'LOGIN_START':
return { ...state, isLoading: true, error: null };
case 'LOGIN_SUCCESS':
return { ...state, isLoading: false, user: action.payload };
case 'LOGIN_ERROR':
return { ...state, isLoading: false, error: action.payload };
case 'LOGOUT':
return initialState;
default:
return state;
}
}
// Separate state and dispatch contexts for optimization
const AuthStateContext = createContext<AuthState | null>(null);
const AuthDispatchContext = createContext<React.Dispatch<AuthAction> | null>(null);
function AuthProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(authReducer, initialState);
return (
<AuthStateContext.Provider value={state}>
<AuthDispatchContext.Provider value={dispatch}>
{children}
</AuthDispatchContext.Provider>
</AuthStateContext.Provider>
);
}
// Custom hooks
function useAuthState() {
const context = useContext(AuthStateContext);
if (!context) {
throw new Error('useAuthState must be used within AuthProvider');
}
return context;
}
function useAuthDispatch() {
const context = useContext(AuthDispatchContext);
if (!context) {
throw new Error('useAuthDispatch must be used within AuthProvider');
}
return context;
}
Performance Optimization
Split State and Actions
// Problem: All consumers re-render when any value changes
const BadContext = createContext({ count: 0, increment: () => {} });
// Solution: Separate frequently changing values
const CountContext = createContext(0);
const CountActionsContext = createContext({ increment: () => {} });
function CountProvider({ children }: { children: ReactNode }) {
const [count, setCount] = useState(0);
// Memoize actions object
const actions = useMemo(() => ({
increment: () => setCount(c => c + 1),
decrement: () => setCount(c => c - 1),
reset: () => setCount(0),
}), []);
return (
<CountContext.Provider value={count}>
<CountActionsContext.Provider value={actions}>
{children}
</CountActionsContext.Provider>
</CountContext.Provider>
);
}
// Now components can subscribe to only what they need
function DisplayCount() {
const count = useContext(CountContext);
console.log('DisplayCount rendered'); // Only when count changes
return <span>{count}</span>;
}
function IncrementButton() {
const { increment } = useContext(CountActionsContext);
console.log('IncrementButton rendered'); // Never re-renders!
return <button
}
Context Composition
Combine multiple contexts cleanly:
// Compose multiple providers
function AppProviders({ children }: { children: ReactNode }) {
return (
<ThemeProvider>
<AuthProvider>
<SettingsProvider>
<NotificationsProvider>
{children}
</NotificationsProvider>
</SettingsProvider>
</AuthProvider>
</ThemeProvider>
);
}
// Or use a composition helper
type ProviderProps = { children: ReactNode };
type Provider = React.ComponentType<ProviderProps>;
function composeProviders(...providers: Provider[]) {
return function ComposedProvider({ children }: ProviderProps) {
return providers.reduceRight(
(child, Provider) => <Provider>{child}</Provider>,
children
);
};
}
const AppProviders = composeProviders(
ThemeProvider,
AuthProvider,
SettingsProvider,
NotificationsProvider
);
// Usage
function App() {
return (
<AppProviders>
<Router />
</AppProviders>
);
}
Context vs Other State Solutions
| Solution |
Use Case |
| Context |
Dependency injection, theme, auth, rarely changing data |
| useState |
Local component state |
| useReducer |
Complex local state logic |
| Zustand/Jotai |
Frequent updates, performance critical |
| TanStack Query |
Server state, caching |
| Redux |
Large apps, time-travel debugging |
When NOT to Use Context
// ❌ Frequently changing data (causes unnecessary re-renders)
const PositionContext = createContext({ x: 0, y: 0 });
// ✅ Use a proper state library instead
const useMouseStore = create((set) => ({
position: { x: 0, y: 0 },
setPosition: (pos) => set({ position: pos }),
}));
// ❌ Complex nested updates
const FormContext = createContext({
values: {},
errors: {},
touched: {},
// ...many more fields
});
// ✅ Use a form library
const { register, handleSubmit } = useForm();
Common Pitfalls
| Issue |
Cause |
Solution |
| Unnecessary re-renders |
Context value not memoized |
Use useMemo for value |
| "Cannot read undefined" |
Missing Provider |
Add null check or throw in hook |
| Stale closures |
Missing dependencies |
Add to dependency array |
| Performance issues |
Large frequently updating context |
Split into multiple contexts |
Best Practices
- Always create custom hooks for consuming context
- Memoize context value with useMemo
- Split state and dispatch into separate contexts
- Use TypeScript for type safety
- Throw error if context used outside provider
- Don't use context for frequently changing values
- Don't pass entire state when only part is needed
- Don't deeply nest too many providers
When NOT to Use This Skill
- React 19 use() hook - Use
react-19 skill for conditional context reading
- State management libraries - Use Zustand, Redux, or Jotai skills for complex state
- Server state - Use TanStack Query skill for data fetching and caching
- Form state - Use React Hook Form skill for form-specific state
Anti-Patterns
| Anti-Pattern |
Problem |
Solution |
| Context for frequently changing values |
Performance issues, many re-renders |
Use state library (Zustand) or useSyncExternalStore |
| Not memoizing context value |
New object every render, all consumers re-render |
Use useMemo for context value |
| Single context with all state |
Unnecessary re-renders |
Split into multiple focused contexts |
| Not throwing in custom hook |
Poor error messages |
Throw error if context is null/undefined |
| Deeply nested providers |
Hard to read, maintain |
Use provider composition helper |
| Context for local component state |
Unnecessary complexity |
Use useState in component |
| Default value that's never used |
Misleading |
Use null/undefined and throw in hook |
Quick Troubleshooting
| Issue |
Likely Cause |
Fix |
| "Cannot read property of undefined" |
Missing Provider |
Wrap component tree with Provider |
| All consumers re-rendering |
Context value not memoized |
Wrap value in useMemo |
| Context value undefined |
Used outside Provider |
Check Provider wraps component |
| Poor performance |
Large frequently-changing context |
Split context or use state library |
| Type errors |
Wrong context type |
Check TypeScript generic in createContext |
| Stale values |
Missing dependencies |
Add values to useMemo dependencies |
| Nested providers confusing |
Too many providers |
Use composition helper function |
Reference Documentation
1---2name: react-context3description: React Context API for state sharing across component trees. Covers createContext, useContext, Provider patterns, performance optimization, context composition, and when to use vs other state solutions. USE WHEN: user mentions "React Context", "createContext", "useContext", "Provider", "Context API", "dependency injection", asks about "avoiding prop drilling", "global state in React", "context composition" DO NOT USE FOR: React 19 use() hook with Context - use `react-19` skill instead, state management libraries - use specific library skills (Zustand, Redux, etc.), server state - use TanStack Query skill instead4---5# React Context67> **Full Reference**: See [advanced.md](advanced.md) for context selectors with useSyncExternalStore, dependency injection, React 19 use(), testing context, and TypeScript patterns.89> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `react` topic: `context` for comprehensive documentation.1011## Basic Usage1213```tsx14import { createContext, useContext, ReactNode } from 'react';1516// 1. Create context with default value17interface ThemeContextValue {18 theme: 'light' | 'dark';19 toggleTheme: () => void;20}2122const ThemeContext = createContext<ThemeContextValue | null>(null);2324// 2. Create Provider component25function ThemeProvider({ children }: { children: ReactNode }) {26 const [theme, setTheme] = useState<'light' | 'dark'>('light');2728 const toggleTheme = useCallback(() => {29 setTheme(prev => prev === 'light' ? 'dark' : 'light');30 }, []);3132 const value = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);3334 return (35 <ThemeContext.Provider value={value}>36 {children}37 </ThemeContext.Provider>38 );39}4041// 3. Create custom hook for consuming42function useTheme() {43 const context = useContext(ThemeContext);44 if (!context) {45 throw new Error('useTheme must be used within a ThemeProvider');46 }47 return context;48}4950// 4. Use in components51function Header() {52 const { theme, toggleTheme } = useTheme();5354 return (55 <header className={theme}>56 <button onClick={toggleTheme}>57 Switch to {theme === 'light' ? 'dark' : 'light'}58 </button>59 </header>60 );61}6263// 5. Wrap app with provider64function App() {65 return (66 <ThemeProvider>67 <Header />68 <Main />69 </ThemeProvider>70 );71}72```7374---7576## Context with Reducer7778For complex state management:7980```tsx81interface AuthState {82 user: User | null;83 isLoading: boolean;84 error: string | null;85}8687type AuthAction =88 | { type: 'LOGIN_START' }89 | { type: 'LOGIN_SUCCESS'; payload: User }90 | { type: 'LOGIN_ERROR'; payload: string }91 | { type: 'LOGOUT' };9293const initialState: AuthState = {94 user: null,95 isLoading: false,96 error: null,97};9899function authReducer(state: AuthState, action: AuthAction): AuthState {100 switch (action.type) {101 case 'LOGIN_START':102 return { ...state, isLoading: true, error: null };103 case 'LOGIN_SUCCESS':104 return { ...state, isLoading: false, user: action.payload };105 case 'LOGIN_ERROR':106 return { ...state, isLoading: false, error: action.payload };107 case 'LOGOUT':108 return initialState;109 default:110 return state;111 }112}113114// Separate state and dispatch contexts for optimization115const AuthStateContext = createContext<AuthState | null>(null);116const AuthDispatchContext = createContext<React.Dispatch<AuthAction> | null>(null);117118function AuthProvider({ children }: { children: ReactNode }) {119 const [state, dispatch] = useReducer(authReducer, initialState);120121 return (122 <AuthStateContext.Provider value={state}>123 <AuthDispatchContext.Provider value={dispatch}>124 {children}125 </AuthDispatchContext.Provider>126 </AuthStateContext.Provider>127 );128}129130// Custom hooks131function useAuthState() {132 const context = useContext(AuthStateContext);133 if (!context) {134 throw new Error('useAuthState must be used within AuthProvider');135 }136 return context;137}138139function useAuthDispatch() {140 const context = useContext(AuthDispatchContext);141 if (!context) {142 throw new Error('useAuthDispatch must be used within AuthProvider');143 }144 return context;145}146```147148---149150## Performance Optimization151152### Split State and Actions153154```tsx155// Problem: All consumers re-render when any value changes156const BadContext = createContext({ count: 0, increment: () => {} });157158// Solution: Separate frequently changing values159const CountContext = createContext(0);160const CountActionsContext = createContext({ increment: () => {} });161162function CountProvider({ children }: { children: ReactNode }) {163 const [count, setCount] = useState(0);164165 // Memoize actions object166 const actions = useMemo(() => ({167 increment: () => setCount(c => c + 1),168 decrement: () => setCount(c => c - 1),169 reset: () => setCount(0),170 }), []);171172 return (173 <CountContext.Provider value={count}>174 <CountActionsContext.Provider value={actions}>175 {children}176 </CountActionsContext.Provider>177 </CountContext.Provider>178 );179}180181// Now components can subscribe to only what they need182function DisplayCount() {183 const count = useContext(CountContext);184 console.log('DisplayCount rendered'); // Only when count changes185 return <span>{count}</span>;186}187188function IncrementButton() {189 const { increment } = useContext(CountActionsContext);190 console.log('IncrementButton rendered'); // Never re-renders!191 return <button onClick={increment}>+</button>;192}193```194195---196197## Context Composition198199Combine multiple contexts cleanly:200201```tsx202// Compose multiple providers203function AppProviders({ children }: { children: ReactNode }) {204 return (205 <ThemeProvider>206 <AuthProvider>207 <SettingsProvider>208 <NotificationsProvider>209 {children}210 </NotificationsProvider>211 </SettingsProvider>212 </AuthProvider>213 </ThemeProvider>214 );215}216217// Or use a composition helper218type ProviderProps = { children: ReactNode };219type Provider = React.ComponentType<ProviderProps>;220221function composeProviders(...providers: Provider[]) {222 return function ComposedProvider({ children }: ProviderProps) {223 return providers.reduceRight(224 (child, Provider) => <Provider>{child}</Provider>,225 children226 );227 };228}229230const AppProviders = composeProviders(231 ThemeProvider,232 AuthProvider,233 SettingsProvider,234 NotificationsProvider235);236237// Usage238function App() {239 return (240 <AppProviders>241 <Router />242 </AppProviders>243 );244}245```246247---248249## Context vs Other State Solutions250251| Solution | Use Case |252|----------|----------|253| Context | Dependency injection, theme, auth, rarely changing data |254| useState | Local component state |255| useReducer | Complex local state logic |256| Zustand/Jotai | Frequent updates, performance critical |257| TanStack Query | Server state, caching |258| Redux | Large apps, time-travel debugging |259260### When NOT to Use Context261262```tsx263// ❌ Frequently changing data (causes unnecessary re-renders)264const PositionContext = createContext({ x: 0, y: 0 });265266// ✅ Use a proper state library instead267const useMouseStore = create((set) => ({268 position: { x: 0, y: 0 },269 setPosition: (pos) => set({ position: pos }),270}));271272// ❌ Complex nested updates273const FormContext = createContext({274 values: {},275 errors: {},276 touched: {},277 // ...many more fields278});279280// ✅ Use a form library281const { register, handleSubmit } = useForm();282```283284---285286## Common Pitfalls287288| Issue | Cause | Solution |289|-------|-------|----------|290| Unnecessary re-renders | Context value not memoized | Use useMemo for value |291| "Cannot read undefined" | Missing Provider | Add null check or throw in hook |292| Stale closures | Missing dependencies | Add to dependency array |293| Performance issues | Large frequently updating context | Split into multiple contexts |294295## Best Practices296297- Always create custom hooks for consuming context298- Memoize context value with useMemo299- Split state and dispatch into separate contexts300- Use TypeScript for type safety301- Throw error if context used outside provider302- Don't use context for frequently changing values303- Don't pass entire state when only part is needed304- Don't deeply nest too many providers305306## When NOT to Use This Skill307308- **React 19 use() hook** - Use `react-19` skill for conditional context reading309- **State management libraries** - Use Zustand, Redux, or Jotai skills for complex state310- **Server state** - Use TanStack Query skill for data fetching and caching311- **Form state** - Use React Hook Form skill for form-specific state312313## Anti-Patterns314315| Anti-Pattern | Problem | Solution |316|--------------|---------|----------|317| Context for frequently changing values | Performance issues, many re-renders | Use state library (Zustand) or useSyncExternalStore |318| Not memoizing context value | New object every render, all consumers re-render | Use useMemo for context value |319| Single context with all state | Unnecessary re-renders | Split into multiple focused contexts |320| Not throwing in custom hook | Poor error messages | Throw error if context is null/undefined |321| Deeply nested providers | Hard to read, maintain | Use provider composition helper |322| Context for local component state | Unnecessary complexity | Use useState in component |323| Default value that's never used | Misleading | Use null/undefined and throw in hook |324325## Quick Troubleshooting326327| Issue | Likely Cause | Fix |328|-------|--------------|-----|329| "Cannot read property of undefined" | Missing Provider | Wrap component tree with Provider |330| All consumers re-rendering | Context value not memoized | Wrap value in useMemo |331| Context value undefined | Used outside Provider | Check Provider wraps component |332| Poor performance | Large frequently-changing context | Split context or use state library |333| Type errors | Wrong context type | Check TypeScript generic in createContext |334| Stale values | Missing dependencies | Add values to useMemo dependencies |335| Nested providers confusing | Too many providers | Use composition helper function |336337## Reference Documentation338339- [React Context](https://react.dev/reference/react/useContext)340- [Scaling Up with Reducer and Context](https://react.dev/learn/scaling-up-with-reducer-and-context)341- MCP: `mcp__documentation__fetch_docs` → technology: `react`, topic: `context`