Community JavaScript Snippet
When `memo` Actually Stops a Re-render (and When It Does Not)
I once added React.memo everywhere and renders barely changed. Memo only works under specific conditions, and outside those it is dead weight. Three accordions on the trap and the fix.
When `memo` Actually Stops a Re-render (and When It Does Not)
I once added React.memo everywhere and renders barely changed. Memo only works under specific conditions, and outside those it is dead weight. Three accordions on the trap and the fix.
By @ananyaadeyemi
January 8, 2026
·
Updated May 18, 2026
978 views
7
4.2 (12)
This is the case the React docs imply when they introduce memo: the parent re-renders, the child receives the same primitive props, the shallow comparison hits, the child is skipped. The render counter at the bottom is the load-bearing thing to look at. Three parent renders produced one child render, two parent renders with 'Ada' then one with 'Linus' produced one more. If your memo'd components are this shape, memo is doing exactly what you expect. The trick is that almost no real component is this shape, which is what the next accordion is about.
The bug pattern is the most common one in any codebase that has been around for more than a year. <UserCard user={{ id, name }} onSelect={() => handleSelect(id)} items={[1, 2, 3]} /> looks fine: the data is logically the same on every render, after all. But each of those literals is a fresh allocation, so shallow-compare sees three different references and the memo always misses. Worse, the comparison itself costs more than the render it failed to skip for very small components, so the net effect is slower than no memo at all. The diagnostic at the bottom is the question I ask first whenever someone tells me memo is not working: any object, array, or function defined inline at the call site has to be stabilized before memo can help.
Fix (a) is the one I reach for nine times out of ten. useMemo for the data object, useCallback for the handler, identical references survive across renders, the default shallow compare in memo finally hits, and the child stops re-rendering. The price is that you have to remember the dependency arrays for every memoized prop, and a stale dependency turns into a stuck handler. Fix (b) is the surgical version: when the data legitimately comes in fresh on every render, like a user object hydrated from a fetch response, a custom equality function can compare by id rather than by reference. I keep both in the toolbox, but the ordering matters: prefer to stabilize at the source, fall back to deep-equal only when stabilization would be invasive.
