React

Context and performance: what causes re-renders

Context does not re-render the whole app: it re-renders consumers when the value changes identity, and the usual cause is an object built in the provider.

Key points

  • Only the components that read the context re-render, not the whole tree.
  • A value created in the provider's render changes identity on every render and wakes every consumer.
  • Splitting state and actions into two contexts cuts renders without adding a library.

Context does not re-render the whole app: it re-renders the components that read that context when the value changes identity. The cost comes from the value rebuilt in the provider's render, not from context itself.

Who re-renders

A component that calls useContext subscribes to the context. When the provider passes a different value, React re-renders every consumer, including those that read only part of it. Components that do not read the context are untouched.

The value that changes identity

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  // new object on every render: every consumer re-renders
  const value = { theme, setTheme };

  return (
    {children}
  );
}

The provider re-renders on every theme change and builds a new object: the comparison fails and every consumer re-renders even when it reads only setTheme. With useMemo on the value, the object stays the same until theme changes.

Splitting the contexts

The second fix is to split state and actions: one context for the changing value and one for the stable functions. Components that read only the actions stop re-rendering when the theme changes.

One provider, many children

The provider's children also re-render when the provider itself re-renders, unless they are memoised: context is not the only cause. Before adding memo everywhere, check whether the provider is rebuilding the value.

Watching it

npm create vite@latest context-demo -- --template react-ts
cd context-demo
npm install
npm run dev

With the app open, the React DevTools Profiler records one interaction and shows which components re-rendered and why. It is the fastest way to tell a context problem from a props problem.

Live records in the catalogue

Two records that live on context: sidebar, where the open state travels from a provider to its children, and theme-toggle, which reads and writes the theme. The rest sits among the 111,985 components and the 1,556 blocks.

The limitation

Splitting contexts cuts renders but adds nested providers: every level is one more component and an order to respect. Neither fix helps when the value read really does change on every interaction: there the problem is in the data, not in context.

Where to continue

The memoised component that completes the fix is in React.memo; the value computed rather than copied is in derived state; the comparison of headless libraries built on this pattern is in Radix vs Base UI vs React Aria.

Frequently asked questions

Does context re-render every component?

No. Only the components that call useContext on that context and sit below the provider re-render. A component that does not read the context receives no update.

Context or plain props?

Props stay right for two or three levels; context pays when the same value is needed by many distant components. Context does not remove re-renders, it only changes how the value travels.

Does context replace Redux or Zustand?

No. Context distributes a value; it does not handle selectors, caching or partial updates, and every change re-renders all consumers. Complex state needs a store with selectors.

Why does the provider re-render its children too?

Because the children are part of the provider's render, whatever the context does. When the provider rebuilds the value or its own state changes, the children re-render unless they are memoised.

Is useMemo on the provider value mandatory?

No. It matters when the provider has expensive consumers and re-renders often. On a provider that changes once a minute the difference is unmeasurable and the memo only adds code.