Ch. 3 · React

React Context Splitting for Performance

Stop a single context value from re-rendering every consumer by splitting state and dispatch and memoizing the provider value.

~2 min readadvancedupdated Oct 5, 2026

Context is a broadcast mechanism: when its value changes, every consumer re-renders. If one provider carries many unrelated values, a change to any of them re-renders components that only read the others. Splitting the context or memoizing the value isolates those updates.

Before you start

You should be comfortable with context, memo and re-render basics. This article focuses on update scope; it assumes correct provider placement.

Step-by-step walkthrough

Step 1: Give each consumer its own context

Put state and the updater in separate contexts: components that only dispatch do not re-render when the state changes, because they consume the dispatch context. This is the common useState-style split and often removes most unnecessary renders.

Step 2: Memoize the provider value

An inline object value={{ user, setUser }} is a new reference every render, so every consumer re-renders even when the data is equal. Wrap it in useMemo keyed on the actual values so the reference is stable.

Step 3: Reconsider the store for high-frequency updates

Context is a poor fit for values that change many times per second, such as a cursor position or a form draft. Use a subscription-based store, or keep high-frequency state local to the components that need it, so the broadcast does not fan out.

Worked scenario

State and dispatch live in separate contexts so a change re-renders only readers.

const StateContext = createContext(null);
const DispatchContext = createContext(null);

function Provider({ children }) {
  const [state, dispatch] = useReducer(reducer, initial);
  return (
    <StateContext.Provider value={state}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  );
}
TSX

Walk through the example

dispatch is stable across renders, so DispatchContext never changes and its consumers do not re-render when state updates. StateContext changes only when state changes, so only actual readers update. A button that only calls dispatch stays put while the list re-renders, which is the intended isolation.

Common mistake

Passing a fresh object as the provider value, which re-renders all consumers on every provider render even when nothing relevant changed. Another is putting unrelated data in one context, so a theme change re-renders form consumers.

Verify the behavior

Add a render counter to a dispatch-only consumer and confirm it does not re-render when state changes. Log renders before and after memoizing the value and compare the counts. Move a high-frequency value into context and observe the fan-out, then move it local and confirm the renders drop.

Interview exercise

Why split a context into state and dispatch contexts?

Answer and reasoning

Because consumers that only write (dispatch) should not re-render when the value changes. Since dispatch is stable, a separate dispatch context never updates, so button-like consumers stay mounted. Only the readers of the state context re-render, which reduces work without changing the API.

Continue learning

Compare placement in React context scope and memo reference stability. Read the React context performance guidance and try the React interview questions.

More in React

read ✓React · mid

React Context Scope and Update Costs

React Context Scope and Update Costs. Learn the reasoning, a practical example, common mistakes and an interview exercise.

~2 min readread →
read ✓React · mid

React lazy Loading and Code Splitting

Split bundles with lazy and Suspense, work with default exports, and place boundaries so a chunk loads only when needed.

~2 min readread →
esc