Back to Blog
Lesson 13 of the Intermediate React: Hooks, State & Data Patterns course
ReactJune 25, 20263 min read

Structuring State for Performance: Optimizing React Context

Stop React performance bottlenecks caused by the Context API. Learn how to split contexts, memoize values, and prevent unnecessary re-renders in your app.

ReactPerformanceContext APIHooksOptimizationjavascriptfrontend

Previously in this course, we explored Architecting Global State with Context and Reducer to centralize our application logic. While this pattern is powerful, it introduces a common performance trap: whenever the context value changes, every single component consuming that context re-renders, regardless of whether it actually needs the updated data.

In this lesson, we’ll tackle this performance overhead by splitting our contexts and memoizing values to ensure your dashboard remains snappy as it grows.

The Problem: Context as a Performance Bottleneck

When you provide an object as a context value, React performs a reference equality check. If your provider component re-renders (perhaps due to a parent component updating), it creates a new object reference. Every consumer—even those only using a small fraction of the state—will re-render.

If your state object contains both high-frequency updates (like a search input) and low-frequency data (like user preferences), your entire UI tree becomes inefficient.

Strategy 1: Splitting Contexts

The most effective way to optimize is to separate state that changes at different rates. Instead of one "God Context" that holds everything, create smaller, specialized providers.

For our dashboard, we’ll split our state into DashboardData and DashboardSettings.

JSX
const DashboardDataContext = createContext();
const DashboardSettingsContext = createContext();

export const DashboardProvider = ({ children }) => {
  const [data, setData] = useState(/* ... */);
  const [settings, setSettings] = useState(/* ... */);

  return (
    <DashboardDataContext.Provider value={data}>
      <DashboardSettingsContext.Provider value={settings}>
        {children}
      </DashboardSettingsContext.Provider>
    </DashboardSettingsContext.Provider>
  );
};

By splitting these, a change to settings will not trigger a re-render in components that only consume data.

Strategy 2: Memoizing Context Values

Even with split contexts, you must ensure the object passed to the value prop is memoized. If the provider itself re-renders, a new object is created unless you use useMemo.

We already discussed Memoizing Expensive Calculations with useMemo for performance earlier in this course; here, we apply it to the provider value itself.

JSX
const DashboardProvider = ({ children }) => {
  const [data, setData] = useState({ items: [] });
  const [settings, setSettings] = useState({ theme: CE9178">'dark' });

  // Memoize the data object
  const dataValue = useMemo(() => ({ data, setData }), [data]);
  
  // Memoize the settings object
  const settingsValue = useMemo(() => ({ settings, setSettings }), [settings]);

  return (
    <DashboardDataContext.Provider value={dataValue}>
      <DashboardSettingsContext.Provider value={settingsValue}>
        {children}
      </DashboardSettingsContext.Provider>
    </DashboardDataContext.Provider>
  );
};

Hands-on Exercise: Refactor the Dashboard

  1. Identify two pieces of state in your current dashboard project that update at different frequencies.
  2. Create two separate Contexts for these states.
  3. Wrap your application in both Providers.
  4. Use useMemo to stabilize the value passed to each provider.
  5. In a consumer component, use useContext to hook into only the specific context you need.

Common Pitfalls

  • Over-splitting: Don't create a context for every single variable. If two values always change together, keep them in the same context.
  • Forgetting useMemo: Even if you split contexts, if the provider component re-renders and you haven't memoized the value, you are still triggering re-renders in all consumers.
  • Ignoring React.memo: For leaf components that consume context, use React.memo as a secondary line of defense to ensure they only re-render if their props or context values actually change.

Recap

We've moved beyond basic state sharing to professional-grade performance management. By splitting contexts by update frequency and memoizing the values, we prevent the "ripple effect" of re-renders that plagues poorly architected React applications. This is critical for maintaining the high-performance bar we set when we first started Introduction to Context API: Avoiding Prop Drilling in React.

Up next: We will apply these patterns to handle authentication state, ensuring our security layer doesn't slow down our UI.

Similar Posts