Community JavaScript Snippet
Why My Context Provider Was Re-rendering Everything
We added a flame-graph and saw every consumer of `<UserContext>` re-rendering on every keystroke in a sibling. The fix took two days to isolate and three lines to ship.
Why My Context Provider Was Re-rendering Everything
We added a flame-graph and saw every consumer of `<UserContext>` re-rendering on every keystroke in a sibling. The fix took two days to isolate and three lines to ship.
By @amiraprice
May 1, 2026
·
Updated May 18, 2026
538 views
5
4.4 (12)
The trap is that JSX hides the allocation. Writing <UserContext.Provider value={{ user, mutate }}> looks declarative, but { user, mutate } is a fresh object literal on every render and React's context machinery uses Object.is to decide whether to fan out an update. The harness above subscribes one consumer and counts how many times it re-runs; we get three updates from three unrelated parent renders even though user was identical. In a real app this manifests as a sluggish form because the search input drags every consumer along for the ride.
Wrapping the value in useMemo([user, mutate]) keeps the object identity stable across renders that did not change those dependencies. useCallback does the same trick for mutate so it does not get re-created and break the memo. After the fix the consumer fan-out fires only when user (or any other real dependency) actually changes. In practice we run this on every context provider in our app; if the value is destructured into more than two fields, useMemo is on the floor before the second one. The shim at the top makes the snippet runnable here; in a real bundle these come from React directly.
Once the value is memoized, the second wave of context performance work is splitting it. Components that only call dispatch (a save button, a modal opener) should not re-render when data changes, because data and dispatch have different cadences: data ticks on every keystroke, dispatch never. Two contexts give each consumer the smallest dependency it actually needs, and dispatch stays referentially stable forever once you wrap it in useCallback([]). We measured this in a 4k-line dashboard and dropped the average render count per keystroke from 47 to 11. The split is mechanical, but you have to know to look for it.
