memo
Skips re-rendering a component when its parent re-renders but its props have not changed.
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)- Added in React 16.6.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$Component | React component | The component you want to memoize. memo does not modify this component; it returns a new, memoized component instead. Any valid React component is accepted, including function components and forwardRef components. |
$arePropsEqualoptional | (prevProps, nextProps) => boolean= Object.is per prop | A function that accepts the previous props and the new props and returns true if they are equal — that is, if the component will render the same output and behave the same way. Usually you do not specify this. By default React compares each prop with Object.is. |
Return value
React componentA new React component. It behaves the same as the component you passed in, except that React will not always re-render it when its parent re-renders unless its props have changed.
Changelog
- PHP 19.0.0Function components receive `ref` as an ordinary prop, so a memoized component no longer has to be wrapped in `forwardRef` to accept one.
- PHP 16.6.0Introduced as `React.memo`, bringing `PureComponent`-style bailouts to function components.
memo wraps a component so React can skip re-rendering it when the parent re-renders with the same props. Props are compared shallowly, key by key, with Object.is.
When to reach for it
Reach for it after profiling shows a component that re-renders often, usually receives identical props, and is genuinely expensive to render — a long list row, a chart, a rich-text surface. Applying it everywhere adds a comparison per render and buys nothing for cheap components.
Why it so often does nothing
The bailout only happens if the props are actually stable. A parent passing style={{ margin: 4 }}, items={rows.filter(...)} or an inline arrow function hands over a brand-new reference every render, so the shallow comparison fails every time. Stabilise those values with useMemo and useCallback in the parent first; without that step memo is pure overhead. Often the cheaper fix is structural: move the state that changes down into a smaller component, or pass the expensive subtree through children so it is not re-created at all.
What it does not stop
memo only guards against re-renders caused by the parent. The component still re-renders when its own state updates, when a context it reads changes, and when a hook it uses schedules an update.
Example
When to use it
- Stop every row of a 500-row table from re-rendering when only one row's selection state changes.
- Keep an expensive chart from re-rendering while an unrelated sibling input updates on every keystroke.
- Prevent a canvas or map component from re-mounting work when its parent re-renders on a timer.
Frequently asked questions
Why does my memoized component still re-render?
Almost always because a prop is a new reference each render — an inline object, array, or arrow function created in the parent's body. memo compares props with Object.is, and {} is never Object.is-equal to another {}. It also cannot block re-renders triggered by the component's own state or by a context it reads.
Should I wrap every component in memo?
No. Every memo call adds a shallow prop comparison on each parent render, and for cheap components that comparison costs more than the render it is trying to avoid. Profile first, and prefer structural fixes such as moving state down or passing subtrees through children.
What is the difference between memo and useMemo?
memo memoizes a whole component so React can skip re-rendering it; useMemo memoizes a value inside a component so it is not recomputed. They are usually used together: useMemo in the parent keeps the props stable so memo in the child can actually bail out.
More examples
memo is useless until the props are stable
With "stable props" checked, clicking "Re-render parent" leaves the chart's render count frozen. Unchecking it recreates the array and the callback on every render, the shallow comparison fails, and the count climbs in lockstep with the parent — the same memo call, doing nothing at all.
import { memo, useCallback, useMemo, useRef, useState } from "react";
const Chart = memo(function Chart({ series, onPick }) {
const renders = useRef(0);
renders.current += 1;
return (
<p>
Chart of {series.length} points, rendered {renders.current}×{" "}
<button onClick={() => onPick(series[0])}>pick first</button>
</p>
);
});
export default function App() {
const [tick, setTick] = useState(0);
const [picked, setPicked] = useState(null);
const [stable, setStable] = useState(true);
// Stable: same array and same function identity across renders.
const memoSeries = useMemo(() => [1, 2, 3, 4], []);
const memoPick = useCallback(value => setPicked(value), []);
// Unstable: brand new references on every single render.
const series = stable ? memoSeries : [1, 2, 3, 4];
const onPick = stable ? memoPick : value => setPicked(value);
return (
<div>
<button onClick={() => setTick(t => t + 1)}>Re-render parent ({tick})</button>{" "}
<label>
<input
type="checkbox"
checked={stable}
onChange={e => setStable(e.target.checked)}
/>
stable props
</label>
<Chart series={series} onPick={onPick} />
<p>Picked: {picked ?? "nothing"}</p>
</div>
);
}Moving state down instead of memoizing
Typing updates only SearchBox: the state lives in the smallest component that needs it, so ExpensiveList never re-renders and needs no memo at all. This restructuring is usually cheaper and more durable than wrapping the list in memo and then chasing prop stability in the parent.
import { useState } from "react";
function ExpensiveList() {
const rows = Array.from({ length: 5 }, (_, i) => `Row ${i + 1}`);
return (
<ul>
{rows.map(row => (
<li key={row}>{row}</li>
))}
</ul>
);
}
// The input owns the state that changes, so nothing above or beside it re-renders.
function SearchBox() {
const [text, setText] = useState("");
return (
<p>
<input value={text} onChange={e => setText(e.target.value)} placeholder="type here" />{" "}
typed: {text || "(nothing)"}
</p>
);
}
export default function App() {
return (
<div>
<SearchBox />
<ExpensiveList />
</div>
);
}
Discussion