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:

bash
⚠ Header rendered 47 times in 10 seconds
  Cause: AppContext value changed
  Time spent: 34ms per render

How 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