![]()
Optimizing React Context Usage to Avoid Performance Pitfalls
While React Context is powerful for state management, overusing it or using it incorrectly can lead to significant performance issues. Here’s how to leverage Context efficiently without compromising your app’s performance.
Common Context Misuses That Hurt Performance
// ❌ Problematic patterns:
// 1. Single giant context for all state
const AppContext = createContext();
function App() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [preferences, setPrefs] = useState({});
// 10+ more state values...
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme, preferences, setPrefs /*...*/ }}>
<Header />
<Main />
<Footer />
</AppContext.Provider>
);
}
// 2. Frequently changing values causing unnecessary re-renders
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
// New object reference on every render
const value = { theme, setTheme };
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
Performance-Optimized Context Patterns
1. Split Contexts by Concern
// ✅ Better - separate contexts
const UserContext = createContext();
const ThemeContext = createContext();
const PreferencesContext = createContext();
function App() {
return (
<UserProvider>
<ThemeProvider>
<PreferencesProvider>
<Header />
<Main />
<Footer />
</PreferencesProvider>
</ThemeProvider>
</UserProvider>
);
}
2. Memoize Context Value
// ✅ Prevent unnecessary re-renders
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
// Memoize the context value
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
3. Use Context Selectors (with libraries)
// ✅ Only re-render when specific value changes
import { useContextSelector } from 'use-context-selector';
function ThemeButton() {
const theme = useContextSelector(ThemeContext, v => v.theme);
return <button className={theme}>Submit</button>;
}
Advanced Optimization Techniques
1. Compound Components with Context
// ✅ Precise updates
const TabsContext = createContext();
function Tabs({ children }) {
const [activeTab, setActiveTab] = useState(0);
const value = useMemo(() => ({ activeTab, setActiveTab }), [activeTab]);
return (
<TabsContext.Provider value={value}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function Tab({ index, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
// Only this button re-renders when activeTab changes
return (
<button
className={activeTab === index ? 'active' : ''}
onClick={() => setActiveTab(index)}
>
{children}
</button>
);
}
2. Context + useReducer for Complex State
// ✅ More efficient than multiple useState
const UserContext = createContext();
function UserProvider({ children }) {
const [state, dispatch] = useReducer(userReducer, initialState);
// Memoize both state and dispatch
const value = useMemo(() => ({ state, dispatch }), [state]);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
}
// Consumer only re-renders when its slice of state changes
function UserProfile() {
const { state: { user } } = useContext(UserContext);
return <div>{user.name}</div>;
}
When Not to Use Context
- High-frequency updates (use state management libraries instead)
- Local component state (use useState/useReducer directly)
- Props drilling just 1-2 levels (might be simpler than context)
- Static values that never change (regular imports might be better)
Performance Monitoring
Check if your context is causing performance issues:
- Use React DevTools Profiler
- Look for unnecessary re-renders in components
- Check component render times
- Verify memoization is working
// Example of checking re-renders
function UserProfile() {
const { user } = useContext(UserContext);
console.log('UserProfile rendered'); // Check console
return <div>{user.name}</div>;
}
Best Practices Summary
- Split contexts by domain/feature
- Memoize context values with useMemo
- Use selectors for frequently changing values
- Combine with useReducer for complex state
- Avoid putting rarely used values in context
- Consider alternatives for high-frequency updates
Remember that Context is ideal for:
- Theme/UI preferences
- Authentication state
- Localized content
- Infrequently updated app-wide state
For other cases, consider:
- Component composition
- State management libraries (Redux, Zustand)
- Server state management (React Query, SWR)
