The Problem
React Context is incredibly convenient for global state, but it has a dangerous default: every component that calls useContext(MyContext) re-renders whenever the context value changes -- even if the component only uses a small part of that value.
Symptoms
- Typing in one input causes unrelated components to re-render
- React DevTools Profiler shows hundreds of components rendering on every keystroke
- App becomes sluggish as the component tree grows
- Performance warning in React DevTools: "This component re-rendered X times"
The Root Cause
```javascript // BAD: Entire object changes every render function App() { const [user, setUser] = useState({ name: '', email: '' }); const [theme, setTheme] = useState('light'); const [notifications, setNotifications] = useState([]);
// New object reference every render = all consumers re-render const value = { user, setUser, theme, setTheme, notifications, setNotifications };
return ( <AppContext.Provider value={value}> <Header /> {/* Re-renders when notifications change */} <Sidebar /> {/* Re-renders when user changes */} <Main /> {/* Re-renders when theme changes */} </AppContext.Provider> ); } ```
Even if only notifications changes, all three components re-render because the entire context value object is a new reference.
Real Error Pattern
You won't see an error message -- this is a silent performance bug. The React DevTools Profler reveals it:
ā Header rendered 47 times in 10 seconds
Cause: AppContext value changed
Time spent: 34ms per renderHow to Fix It
Fix 1: Split Context by Domain
```javascript const UserContext = createContext(); const ThemeContext = createContext(); const NotificationContext = createContext();
function App() { const [user, setUser] = useState({ name: '', email: '' }); const [theme, setTheme] = useState('light'); const [notifications, setNotifications] = useState([]);
return ( <UserContext.Provider value={{ user, setUser }}> <ThemeContext.Provider value={{ theme, setTheme }}> <NotificationContext.Provider value={{ notifications, setNotifications }}> <Header /> {/* Only subscribes to NotificationContext */} <Sidebar /> {/* Only subscribes to UserContext */} <Main /> {/* Only subscribes to ThemeContext */} </NotificationContext.Provider> </ThemeContext.Provider> </UserContext.Provider> ); } ```
Fix 2: Memoize the Context Value
```javascript function App() { const [user, setUser] = useState({ name: '', email: '' }); const [theme, setTheme] = useState('light');
const value = useMemo(() => ({ user, setUser, theme, setTheme }), [user, theme]);
return <AppContext.Provider value={value}>...</AppContext.Provider>; } ```
Fix 3: Use a Selector Pattern
```javascript function useContextSelector(context, selector) { const value = useContext(context); return useMemo(() => selector(value), [value, selector]); }
// Usage: only re-renders when user.name changes const name = useContextSelector(AppContext, ctx => ctx.user.name); ```
Prevention
- Split large contexts into smaller, focused ones
- Always wrap context values in
useMemo - Use React DevTools Profiler regularly to spot unnecessary renders
- Consider Zustand or Jotai for complex state management