Overusing React Context leading to performance issues

Loading

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

  1. High-frequency updates (use state management libraries instead)
  2. Local component state (use useState/useReducer directly)
  3. Props drilling just 1-2 levels (might be simpler than context)
  4. Static values that never change (regular imports might be better)

Performance Monitoring

Check if your context is causing performance issues:

  1. Use React DevTools Profiler
  2. Look for unnecessary re-renders in components
  3. Check component render times
  4. 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

  1. Split contexts by domain/feature
  2. Memoize context values with useMemo
  3. Use selectors for frequently changing values
  4. Combine with useReducer for complex state
  5. Avoid putting rarely used values in context
  6. 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)

Leave a Reply

Your email address will not be published. Required fields are marked *