When Context Optimization Actually Matters

Before reaching for optimization tricks, it's worth being honest about whether your React context is actually a bottleneck. The cases that genuinely benefit from optimizing your context value share a specific profile:

  • The context value updates frequently
  • Many components consume that context
  • You're already using React.memo because rendering is measurably slow
  • You've profiled the app and confirmed context re-renders are the culprit

If that doesn't describe your situation, stop here. React handles most rendering patterns efficiently on its own, and adding complexity to optimize something that isn't slow just wastes your maintenance budget. If you do have all those conditions, the first thing to know is that the simplest fix often isn't memoization at all — it's restructuring how your context is split.

Separating State from Dispatch

Instead of useMemo-wrapping a combined context object, you can sidestep the problem entirely by using useReducer or useState and exposing the state and the update function through two separate context providers. The state provider holds the current value; the dispatch provider only exposes the updater.

clicking "force render" three times and "Increment count" twice

Because components that only consume the updater context never receive the state value, their context doesn't change when the state changes — so they don't re-render at all. A counter component that only reads dispatch stays untouched on every state update, which useMemo alone wouldn't achieve since re-renders would still cascade down through the tree.

That said, this two-context setup is a more verbose API than most applications need. It's worth adopting only when you've actually hit the performance problems described above — it's a workaround, not a pattern to apply everywhere by default.

One Context When You Prefer Simplicity

The split-context approach can feel overly ceremonial for smaller components:

const state = useCountState()
const dispatch = useCountDispatch()

A more compact version that merges state and dispatch into a single context provider is possible:

const [state, dispatch] = useCount()

You can absolutely write it that way:

function useCount() {
	return [useCountState(), useCountDispatch()]
}

Just be aware of the tradeoff: any component consuming that unified context gets both the state and the updater, so you lose the selective re-render benefit. Components that only need one half will still re-render when the other half changes.

If you're working through how to structure context effectively in general, it's worth reviewing the broader guidance on React Context usage — the separation pattern here is one piece of a larger set of best practices.