React memo Explained: When It Helps and When It Does Not

React.memo skips re-rendering a component when its props are unchanged, but only when those props keep a stable identity. Learn when it helps and when it adds nothing.

6 min read

memo is a React API that lets a component skip re-rendering when its props are unchanged. It is stable, built into React, and imported from the react package. It is purely a performance optimization, not a correctness guarantee.

How memo decides to skip

By default, when a parent re-renders, React re-renders all of its children. memo changes that for one component. React compares each prop with Object.is, and if every prop is unchanged, it reuses the previous output and skips calling the component.

App.jsxApp.jsx
import { memo, useState } from "react";
 
export default function App() {
  const [name, setName] = useState("Taylor");
  const [address, setAddress] = useState("");
 
  return (
    <>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <input value={address} onChange={(e) => setAddress(e.target.value)} />
      <Greeting name={name} />
    </>
  );
}
 
const Greeting = memo(function Greeting({ name }) {
  return <h3>Hello, {name}!</h3>;
});

Greeting re-renders when the name input changes because name is one of its props. It does not re-render when the address input changes, because address is not passed to it and nothing else about it changed.

When memo helps

memo pays off when a component re-renders often with identical props and its render work is expensive. The telltale sign in the Profiler is a component that renders on commits where none of its inputs changed. That component is doing repeat work for no reason.

A dashboard is a common case. A chart component sits below a panel, and typing in a search box above it re-renders the chart on every keystroke even though the chart data never changed.

When you find one, wrapping it in memo lets it skip those extra renders. But only apply it after you confirm the component is actually the bottleneck. Recording the interaction first is the reliable way to tell, which the Profiler workflow covers in detail.

When memo does not help

memo adds nothing in three common cases.

  • A prop is recreated on every render, such as a new object, array, or function.
  • The component's own state changes.
  • A context the component reads changes.

The first case is the most surprising. A prop that is always new means the comparison always fails, so the component re-renders every time anyway.

The other two are by design. memo only concerns props from the parent, so it never blocks a render caused by the component's own state or by a context value it reads.

App.jsxApp.jsx
import { memo, useState } from "react";
 
export default function App({ items }) {
  const [selected, setSelected] = useState(null);
 
  return (
    <ul>
      {items.map((item) => (
        <Row key={item.id} item={item} onSelect={() => setSelected(item.id)} />
      ))}
    </ul>
  );
}
 
const Row = memo(function Row({ item, onSelect }) {
  return <li onClick={onSelect}>{item.title}</li>;
});

The inline arrow creates a new onSelect function on every render, so Row receives a fresh prop each time and the memo check never succeeds. Passing a handler that reads the id from the event, or extracting an item component that owns the handler, keeps the prop stable. For values and functions that must keep their identity, useMemo and useCallback fill that gap.

When memo skips a child render

The diagram shows the check memo performs on every parent render. The comparison is shallow: React compares prop references with Object.is, not the contents of objects and arrays. Two different objects with the same values still count as changed.

memo and React Compiler

When React Compiler is enabled, it applies the equivalent of memo automatically, which means you usually no longer need to wrap components by hand. React Compiler tracks which values flow into each component and reuses the previous output when nothing relevant changed. That makes manual memo mostly a fallback for codebases that have not adopted the compiler yet.

Common mistakes

Most memo mistakes come from treating it as a correctness tool or from unstable props.

  • Adding memo everywhere before measuring which component is slow.
  • Passing a new object, array, or function and expecting memo to help.
  • Writing a custom comparison function that compares only some props. It must compare every prop, including functions, or the component can close over stale values.
  • Expecting memo to block renders caused by state or context changes.
Rune AI

Rune AI

Key Insights

  • memo compares props with Object.is and skips the render when all props are unchanged.
  • Own state or a changed context still re-renders a memoized component.
  • New objects, arrays, or functions on every render defeat memo.
  • Measure with the Profiler before wrapping components.
  • React Compiler applies this optimization automatically.
RunePowered by Rune AI

Frequently Asked Questions

Does memo make the first render faster?

No. memo only skips re-renders after the initial mount. The first render always runs the component in full.

Why does my memoized component still re-render?

Usually because a prop is a new object, array, or function on every render, or because the component uses state or context that changed. Compare each prop with Object.is to find which one is new.

Conclusion

memo is a useful tool when a component re-renders often with identical props and the render is expensive. It adds nothing when props are always new, and it is unnecessary for components that are already fast. Measure first, then wrap only the components the Profiler points to.