Quiz

What are some pitfalls about using context in React?

Topics
React

TL;DR

Context in React is convenient but easy to misuse. The biggest pitfalls are passing a fresh object/array as the provider value on every render (which forces every consumer to re-render), assuming React.memo will stop context-driven re-renders (it won't), and reaching for context as a general-purpose state manager. For frequently-changing or independent slices of state, split context into multiple providers, memoize the value, or use a dedicated state library like Redux, Zustand, or Jotai.


Pitfalls of using context in React

Unnecessary re-renders from an unstable provider value

When a context value changes by reference, every component that reads that context re-renders — even if it only uses a field that hasn't actually changed. The most common cause is constructing a new object inline as the provider's value, which makes a fresh reference on every parent render:

// Pitfall — `value` is a new object every render, so every consumer re-renders
function ParentComponent() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
return (
<MyContext.Provider value={{ user, setUser, theme, setTheme }}>
<ChildComponent />
</MyContext.Provider>
);
}

Fix it by memoizing the value (or letting the React Compiler do it for you):

function ParentComponent() {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const value = useMemo(
() => ({ user, setUser, theme, setTheme }),
[user, theme],
);
return (
<MyContext.Provider value={value}>
<ChildComponent />
</MyContext.Provider>
);
}

Note: only consumers of the context re-render when the value changes — not "all components in the subtree." Components that don't call useContext/use(MyContext) are unaffected.

React.memo doesn't stop context-driven re-renders

A common surprise: wrapping a consumer in React.memo does not prevent re-renders triggered by a context value change. memo only skips re-renders caused by changing props. If the component reads a context whose value changed, it re-renders regardless. The fix is to make the context value stable (above) or split the context.

Putting too much unrelated state in one context

If you cram an entire app's state into a single context, every change to any slice re-renders every consumer. Split it into smaller, focused providers — for example, separate AuthContext, ThemeContext, and CartContext — so that a cart update doesn't re-render every theme consumer. You can also split read and write APIs into separate contexts so components that only need to dispatch don't re-render when state changes.

No built-in selectors

Unlike Redux's useSelector, React context has no built-in way to subscribe to a slice of the value. Any change to the value re-runs every consumer. Workarounds include:

  • Splitting the context into smaller pieces (preferred).
  • The community use-context-selector library, which adds selector-based subscriptions.

Using context as a state manager

Context is a transport mechanism for passing values down the tree, not a state manager. It has no caching, no devtools, no middleware, no fine-grained updates. For complex client state, prefer Redux Toolkit, Zustand, or Jotai. For server state (data from APIs), use TanStack Query, SWR, or RTK Query — don't store fetched data in context.

Debugging difficulties

Because context updates can fan out across the tree, tracking down which provider caused a re-render can be hard, especially with nested providers. The React DevTools "Profiler" tab and "Why did this render?" highlighting help here, but it's still a good reason to keep providers small and focused.

React 19: use(Context) instead of useContext

In React 19 you can read a context with the new use API. Unlike useContext, use can be called inside conditionals and loops:

import { use } from 'react';
function Profile() {
const user = use(UserContext);
return <div>{user.name}</div>;
}

useContext still works and is not going away — use(Context) is just more flexible.

Further reading

Edit on GitHub