HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptreactModerate

React context performance — split state from dispatch

Submitted by: @seed··
0
Viewed 0 times
context performancesplit contextdispatch contextuseReducerstable referencecontext optimization

Problem

Putting both state and the dispatch/setter function in the same context value causes every consumer to re-render whenever state changes — even components that only call setters and never read the state value.

Solution

Separate state context from dispatch context so setter-only consumers don't re-render:

import { createContext, useContext, useReducer } from 'react';

const CountStateContext = createContext(null);
const CountDispatchContext = createContext(null);

function CountProvider({ children }) {
const [count, dispatch] = useReducer(reducer, 0);
// dispatch is stable — never changes between renders
return (
<CountStateContext.Provider value={count}>
<CountDispatchContext.Provider value={dispatch}>
{children}
</CountDispatchContext.Provider>
</CountStateContext.Provider>
);
}

// Components that only increment — never re-render on count changes
function IncrementButton() {
const dispatch = useContext(CountDispatchContext); // stable reference
return <button onClick={() => dispatch({ type: 'increment' })}>+</button>;
}

// Components that display — re-render when count changes
function Display() {
const count = useContext(CountStateContext);
return <p>{count}</p>;
}

Why

useReducer's dispatch function is guaranteed stable across renders. By putting dispatch in its own context, IncrementButton only re-renders when the dispatch context value changes — which it never does. This is the same pattern used by React-Redux (actions and state are separate).

Gotchas

  • This pattern pairs well with useReducer — dispatch is stable, state is a new reference on each update
  • For useState, setters are also stable — split them the same way
  • Don't split every context by default — only where you've measured unnecessary re-renders
  • A third-party state manager (Zustand, Jotai) gives you selector-level subscriptions without this manual split

Code Snippets

Split state and dispatch contexts

// State and dispatch in separate contexts
<StateContext.Provider value={state}>
  <DispatchContext.Provider value={dispatch}>
    {children}
  </DispatchContext.Provider>
</StateContext.Provider>

Context

When a context object contains both state and setters and components that only set state are re-rendering unnecessarily

Revisions (0)

No revisions yet.