LearnContact
Lesson 4820 min read

React.memo

Learn how React.memo skips unnecessary re-renders by comparing props, and why it does not help when new function or object props are created every render.

Introduction

By default, when a parent component re-renders, React re-renders all of its child components too — even children whose props did not actually change. Usually this is fast enough to be invisible, but in components that do expensive rendering work, those unnecessary re-renders can add up.

React.memo is a built-in tool that lets a component skip re-rendering when its props are the same as last time, giving you a simple way to optimize components that re-render more often than necessary.

What You Will Learn
  • What React.memo does and how its shallow comparison works.
  • A worked example comparing a component with and without memo.
  • How to pass a custom comparison function as the second argument.
  • Why React.memo does not help if you pass a brand-new function or object prop every render.

What Does React.memo Do?

React.memo() is a function that wraps a component and returns a memoized version of it. Before re-rendering that memoized component, React compares its new props against its previous props using a shallow comparison — checking whether each prop value is === to the previous one.

If every prop is equal, React skips re-rendering that component entirely and reuses the previous render output. If any prop changed, the component re-renders as normal.

Wrapping a Component with memo
const Child = React.memo(function Child({ label }) {
  console.log("Child rendered");
  return <p>{label}</p>;
});

Skips Re-renders

If props are shallow-equal to last time, the component is not re-rendered.

Shallow Comparison

Primitives are compared by value; objects and functions are compared by reference (===).

Opt-in Optimization

You choose which components benefit; not every component needs memo.

Best for Pure Components

Works best on components that render the same output for the same props.

Without memo vs With memo

Consider a parent component with a counter and a child that just displays a label. Every time the counter updates, the parent re-renders — and without memo, the child re-renders too, even though its label prop never changes.

Without memo — Child re-renders every time
function Child({ label }) {
  console.log("Child rendered");
  return <p>Label: {label}</p>;
}

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <Child label="Static Label" />
    </div>
  );
}
Console Output After Clicking the Button 3 Times
Child rendered
Child rendered
Child rendered
Child rendered   (once on mount, then once per click)

Now wrap Child in React.memo. Since its label prop never changes, React skips re-rendering it on every click:

With memo — Child render is skipped
const Child = React.memo(function Child({ label }) {
  console.log("Child rendered");
  return <p>Label: {label}</p>;
});

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Count: {count}
      </button>
      <Child label="Static Label" />
    </div>
  );
}
Console Output After Clicking the Button 3 Times
Child rendered   (only once, on the initial mount)
What Changed

Only Parent re-renders on each click, because only its own count state changed. Child's props (label) stayed exactly the same, so memo lets React reuse the previous render.

Custom Comparison Functions

The default shallow comparison works well for most cases, but sometimes you need custom logic — for example, ignoring a prop that changes often but does not affect the rendered output. React.memo accepts an optional second argument: a function that receives the previous and next props and returns true if they should be treated as equal (skip re-render).

Custom Comparison Function
const Child = React.memo(
  function Child({ user, lastSeen }) {
    return <p>{user.name}</p>;
  },
  (prevProps, nextProps) => {
    // Only re-render if the user's name actually changed;
    // ignore lastSeen updates entirely.
    return prevProps.user.name === nextProps.user.name;
  }
);
Return Value Is Reversed From Most Comparisons

The comparison function returns true when props are equal (meaning: skip the re-render), which is the opposite of how shouldComponentUpdate worked in class components. Double-check the boolean direction when writing one.

The New Function/Object Prop Trap

React.memo's shallow comparison checks object and function props by reference. If the parent creates a brand-new function or object literal on every render — which is the default behavior for inline arrow functions and object literals in JSX — that prop will never be === to the previous one, and memo will never be able to skip the render.

memo Defeated by a New Function Every Render
const Child = React.memo(function Child({ onClick }) {
  console.log("Child rendered");
  return <button onClick={onClick}>Click me</button>;
});

function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* New arrow function created on every Parent render! */}
      <Child onClick={() => console.log("clicked")} />
    </div>
  );
}
Console Output
Child rendered
Child rendered
Child rendered   (memo has no effect — onClick is a new function every time)

To actually benefit from memo here, the function passed down needs a stable reference across renders. That is exactly what useCallback (and useMemo for object props) provides, as covered in the earlier Hooks lessons.

Fixed with useCallback
function Parent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log("clicked");
  }, []); // stable reference across renders

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <Child onClick={handleClick} />
    </div>
  );
}
Console Output
Child rendered   (only once — onClick stays the same reference, so memo can skip re-renders)

Common Mistakes

Avoid These Mistakes
  • Wrapping every component in React.memo "just in case" — the comparison itself has a small cost, and it only pays off for components that actually re-render often with unchanged props.
  • Passing inline arrow functions or object/array literals as props to a memoized component, which silently defeats the memoization.
  • Forgetting that memo only affects whether the component re-renders due to prop changes — it does not stop re-renders caused by that component's own state or context changes.
  • Writing a custom comparison function that returns the opposite boolean than intended.

Best Practices

  • Reach for React.memo when profiling shows a component re-rendering unnecessarily and doing meaningful rendering work.
  • Pair memo with useCallback and useMemo for any function or object props you pass down.
  • Prefer the default shallow comparison; only add a custom comparison function when you have a specific, well-understood reason.
  • Do not treat memo as a default — plain components without it are simpler and are often fast enough.

Frequently Asked Questions

Is React.memo the same as useMemo?

No. React.memo wraps a whole component to control whether it re-renders. useMemo memoizes a computed value inside a component. They solve related but different problems.

Does React.memo make a component render faster?

No, it does not speed up a single render. It skips renders entirely when props have not changed, which can reduce the total number of renders.

Do I need React.memo for every component?

No. Most components are cheap enough that re-rendering has no noticeable cost. Use memo selectively, based on measured performance issues.

Why is my memoized component still re-rendering?

The most common cause is a prop that is a new function, array, or object reference each render. Check whether inline functions or literals are being passed, and wrap them with useCallback/useMemo.

Can React.memo be combined with a custom comparison and children props?

It can, but comparing children props is tricky since JSX children are also new objects each render. In practice, custom comparisons work best for simple, primitive-heavy prop sets.

Key Takeaways

  • React.memo wraps a component so it skips re-rendering when its props are shallowly equal to the previous render.
  • Without memo, a child re-renders whenever its parent re-renders, even with unchanged props.
  • A second argument to React.memo lets you supply a custom comparison function.
  • Passing a brand-new function or object prop every render defeats memo — pair it with useCallback and useMemo.

Summary

React.memo gives you a simple way to skip re-rendering components whose props have not changed, which you saw demonstrated with a Child component that stopped re-rendering once wrapped in memo. You also saw how new function/object props can silently defeat this optimization, and how useCallback fixes that.

Next, you will look at code splitting, a technique for breaking your app's JavaScript bundle into smaller pieces that load on demand.

Lesson 48 Completed
  • You understand what React.memo does.
  • You compared rendering with and without memo.
  • You can write a custom comparison function.
  • You know why new function/object props defeat memoization.
Next Lesson →

Code Splitting