David John Thammineni
React19 Nov 20202 min read

React Context Without the Re-Render Tax

React Context Without the Re-Render Tax

Context is React's built-in dependency injection, and it's great — until a fast-changing value starts re-rendering half your tree. The mechanics are simple once stated plainly: when a Provider's value changes identity, every consumer re-renders.

Mistake 1: the inline object

// Every render of App creates a new object -> all consumers re-render
<AuthContext.Provider value={{ user, login, logout }}>

Fix with useMemo:

const value = useMemo(() => ({ user, login, logout }), [user]);
<AuthContext.Provider value={value}>

(login/logout should themselves be stable via useCallback.)

Mistake 2: one mega-context

If theme, auth, and a rapidly-updating notification count live in one context, toggling the notification badge re-renders every theme consumer. Split by change frequency:

<ThemeContext.Provider value={theme}>        {/* changes rarely */}
  <AuthContext.Provider value={auth}>        {/* changes on login */}
    <NotificationsContext.Provider value={n}> {/* changes constantly */}
      <App />

Consumers subscribe only to the slice they read. This one refactor fixed a client app where typing in a form stuttered because keystrokes updated a context consumed by the entire shell.

Mistake 3: state and dispatch together

Components that only trigger actions shouldn't re-render when state changes:

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

// A button that only dispatches never re-renders on state change
const dispatch = useContext(DispatchContext);

When to move on

If you're memoizing aggressively and still fighting re-renders, the data is probably app state, not contextual environment. Libraries like Redux (with selector-based subscriptions) re-render only components whose selected slice changed — which is precisely the granularity Context can't give you today.

Keep Reading