DesignDev.io logoDesignDev.io logo

  1. Home
  2. /React Foundations
  3. /Why React.memo Doesn't Always Help (And When It Does)
← Back to home

Why React.memo Doesn't Always Help (And When It Does)

Alex Chen

Alex Chen

Cover Image for Why React.memo Doesn't Always Help (And When It Does)
Alex Chen

Alex Chen

January 19, 2026·4 min read
Updated April 26, 2026
Series:React Foundations
Level:Intermediate
Tags:reactweb
Assumes:Comfortable with React hooks, re-renders, and the reconciliation article

Wrapping a component in React.memo and finding it still re-renders on every parent render is one of the most common React performance frustrations. The fix is almost always the same — and it has nothing to do with memo itself.


Here's a situation most React developers encounter once and remember forever.

A component is re-rendering too often. You wrap it in React.memo. It still re-renders on every parent render. You stare at it. You add console.log to confirm. It's definitely still re-rendering. React.memo appears to be doing nothing.

This is not a bug in React.memo. It's a reference stability problem — and once you understand it, you'll never be confused by it again.


What React.memo actually compares

React.memo wraps a component and performs a shallow comparison of props between renders. If the props haven't changed, the component skips the render.

Shallow comparison means: for each prop, check if prevProp === nextProp. For primitive values — strings, numbers, booleans — this works perfectly. "hello" === "hello" is true. 42 === 42 is true.

For objects, arrays, and functions, === compares references — memory addresses. Two objects with identical contents are not equal if they're different instances:

This is why React.memo appears to do nothing when your parent component creates objects, arrays, or functions inline — they get new references on every render, and memo sees them as changed props.


The three unstable prop types

Inline objects

Every time Parent renders, { color: 'red', fontWeight: 'bold' } is a new object — different reference, same values. memo's shallow comparison sees a changed prop and re-renders Child.

Fix: move the object outside the component or stabilise it with useMemo.

For truly static values — styles, config objects, constants — moving them outside the component is always cleaner than useMemo. useMemo is for values that depend on props or state.

Inline arrays

Same logic. The array values are identical but the reference is new on every render. Move constants outside the component.

Inline functions

The function is recreated on every render. memo sees a new reference. Fix: useCallback.

Now MemoizedChild only re-renders when userId changes — not when count changes.


Measuring before optimising

Before adding memo, useCallback, and useMemo to a component, confirm there's a real problem.

The React DevTools Profiler is the right tool. Record a session, interact with the component you're concerned about, and examine the flame graph.

If the component renders in 0.3ms, it doesn't matter how often it re-renders. The optimisation overhead — the comparison in memo, the memoization in useCallback — might actually be more expensive than the render you're trying to prevent.

The rule: profile first, optimise second. memo is not a free operation.


When React.memo genuinely helps

After fixing reference instability, memo earns its place in these scenarios:

Large lists with expensive items

A list that renders 200 rows. Each row renders a component that does moderate work — formatting, conditional styling, nested children. When the parent's state changes (a filter, a sort, a different selected item), all 200 rows re-render even if only one changed.

With memo and a stable onSelect (via useCallback), clicking a row to select it only re-renders the previously-selected row (to clear its highlight) and the newly-selected row (to add the highlight). The other 198 rows skip their renders entirely.

Without memo: 200 renders. With memo: 2 renders. That's where the performance win is real and measurable.

Components that receive primitive props

Components whose props are only strings, numbers, and booleans benefit from memo without needing useCallback or useMemo as companions — primitives are compared by value, not reference.

status is a string, count is a number. Both compare by value. If the parent re-renders but neither value changed, StatusBadge skips its render without any additional setup.

Components below a high-frequency state owner

A parent component whose state updates frequently — a text input, a scroll handler, a clock — causes all its children to re-render on every update. If one of those children is expensive and its props don't change with every update, memo prevents unnecessary re-renders.


The custom comparator

React.memo accepts a second argument — a comparison function. Return true to skip the render (props are equal), false to allow it (props changed).

This is useful when a prop object has fields that update frequently but don't affect the component's output. The custom comparator ignores the irrelevant fields.

Use this sparingly. A custom comparator that's wrong will silently prevent renders that should happen — a worse bug than unnecessary renders.


The three-tool mental model

React.memo, useCallback, and useMemo form a system. They're designed to work together.

React.memo — decides whether to re-render based on props comparison useCallback — stabilises function props so memo's comparison works useMemo — stabilises object/array props so memo's comparison works

Using memo without stabilising the props it compares accomplishes nothing. Stabilising props with useCallback and useMemo without memo on the component also accomplishes nothing — the child still re-renders when the parent does.

All three, applied together where a real performance problem exists, in a component with stable primitive or stabilised reference props — that's when the combination genuinely helps.

When all props are either primitives or stabilised references, memo's shallow comparison can actually do its job.


Up next: The render props pattern is not dead — here's where it still wins — the cases where render props outperform hooks and compound components.

Related: Fine-grained reactivity in React — minimising re-renders at scale — the architecture-level complement to component-level memoisation.

Alex Chen is a senior frontend engineer who writes about React patterns, JavaScript internals, and the decisions that separate maintainable codebases from ones that fight back. Opinionated by design.

Photo by Shane Aldendorff on Unsplash


More Stuff

Publishing Your First React Component to npm — the Complete Setup
April 1, 2026·3 min read

Publishing Your First React Component to npm — the Complete Setup

Most tutorials stop at "it works on my machine." This one doesn't. Publishing to npm is where your library becomes real — installable, versioned, and usable by anyone. Here's the complete setup, including the fields most developers get wrong in package.json and how to inspect what you're actually shipping before it goes live.

Muhammad Athar

Muhammad Athar

How to structure a UI library that scales — folders, exports, and naming conventions
April 1, 2026·3 min read

How to structure a UI library that scales — folders, exports, and naming conventions

The folder structure you pick on day one is the one you'll live with across 40 components. Get it wrong and you'll be fighting your own codebase by article 10. Get it right and adding a new component takes five minutes and follows a pattern every contributor can learn in one sitting.

Muhammad Athar

Muhammad Athar

DesignDev.io logoDesignDev.io logo

Explore

  • Home
  • Search
  • Authors
  • Series
  • About Us

Account

  • Sign in

Legal

  • Privacy Policy
  • Terms of Service

© 2026 DesignDev.io · All rights reserved

Built with Next.js · Tailwind · Sanity · Vercel