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.
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.
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.
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
memoeverywhere before measuring which component is slow. - Passing a new object, array, or function and expecting
memoto 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
memoto block renders caused by state or context changes.
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.
Frequently Asked Questions
Does memo make the first render faster?
Why does my memoized component still re-render?
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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.