React.memo

Skip a Re-Render When Props Are Unchanged

React.memo

`memo` wraps a component so it skips re-renders when its props haven't changed. Useful, but easy to overuse.

4 min read Level 2/5 #react#memo#performance
What you'll learn
  • Wrap a component in `memo`
  • Understand the "same props" check
  • Recognize when memo helps and when it doesn't

memo(Component) returns a wrapped component that skips its render when its props haven’t changed (by shallow comparison).

The Shape

import { memo } from "react";

const ExpensiveList = memo(function ExpensiveList({ items }) {
  // heavy render
  return items.map(item => <Item key={item.id} item={item} />);
});

When the parent re-renders, React first compares ExpensiveList’s new props to its previous props. If equal, it skips the render entirely.

Shallow Equality

memo compares props with Object.is per key. Same primitives → considered equal. New object/array literal → different reference → re-render.

// Each render: new `items` array reference, even if contents are the same
return <ExpensiveList items={data.filter(/* ... */)} />;

That filter(...) returns a new array every render, so memo can’t help. You’d need to useMemo the array first to keep its reference stable.

When It Actually Helps

  • The component is slow to render (large lists, complex layouts)
  • The component receives mostly the same props on most renders
  • The parent re-renders often for reasons unrelated to this child

When it doesn’t help:

  • Fast components (most of them)
  • Components whose props change every render anyway
  • Whole-app premature optimization

The Test

Open React Profiler. If a slow component renders far more often than its props actually change → memo is a good fit.

A Common Pairing

memo + useCallback + useMemo:

const Row = memo(function Row({ item, onSelect }) {
  /* ... */
});

function List({ data }) {
  const onSelect = useCallback(id => save(id), []);
  return data.map(item => <Row key={item.id} item={item} onSelect={onSelect} />);
}

Without useCallback, every render passes a new onSelect → memo can’t skip. Without memo, useCallback saves nothing. The trio works together.

Custom Equality Function

Second argument: a custom equality function (prev, next) => boolean. Returns true to skip the render.

const Row = memo(
  function Row({ item }) { /* ... */ },
  (prev, next) => prev.item.id === next.item.id   // ignore other fields
);

Rare. Reach for it only when shallow equality misses an obvious performance win.

Up Next

A clever pattern — using a key to reset a component’s state.

Keys As Reset →