memo lets a component skip re-rendering when its props are the same as last time. Wrap a component in memo(...) and, when its parent renders, React first compares each new prop with the old one; if all of them are equal, it reuses the previous result instead of calling the component again.
Type in the input: it keeps up, and the Console shows render SlowList only once, from the first render. Remove the memo( wrapper (and its closing parenthesis) and type again: every keystroke now waits 150 ms for the list and logs a render.
The syntax
import { memo } from 'react';
const MemoizedComponent = memo(SomeComponent, arePropsEqual?);
SomeComponentis any function component. The usual style is to wrap the definition directly:const Card = memo(function Card(props) { ... }). Naming the inner function keeps the name in React DevTools and in error messages.arePropsEqualis optional and rarely needed (see below).- The result is a component you render like any other.
By default React compares the old and new props shallowly: it checks each prop with Object.is. Strings, numbers and booleans compare by value, so title="Hi" equals title="Hi". Objects, arrays and functions compare by reference: two objects with the same contents are still different objects.
In the example above, items is declared outside the component, so it is the same array on every render, and the comparison passes.
New objects and functions defeat memo
Anything written as a literal inside the parent's body is created again on every render: { name: 'Ada' }, [1, 2, 3], () => save(). Pass one to a memo component and the prop is never equal to the previous one.
Each click logs render Badge object prop but never the string version. There are three ways to fix the object case:
- Pass primitives.
name="Ada"instead ofuser={{ name: 'Ada' }}when the child only needs a field or two. - Move constants out of the component. An object that never changes can live at module level, like
itemsin the first example. - Cache it. Wrap objects and arrays built from props or state in useMemo, and functions in useCallback.
children has the same problem. <Card><p>Hello</p></Card> creates a new <p> element object on every render, so a memo Card that receives children re-renders every time.
What memo does not stop
memo only looks at props coming from the parent. A memoized component still renders when:
- its own state changes, through its own
useStateoruseReducer; - a context it reads changes, through
useContext.
Click Unrelated and Panel is skipped. Switch theme and it renders, because it reads the context. Click Like and it renders, because its own state changed. That is the intended behaviour: memo is about skipping work when nothing the component depends on changed, and context and state are things it depends on.
A custom comparison function
The second argument replaces the shallow comparison. It receives the previous and next props and returns true when they should be treated as equal, meaning React skips the render.
const MemoChart = memo(Chart, (prev, next) => {
return (
prev.points.length === next.points.length &&
prev.points.every((p, i) => p === next.points[i]) &&
prev.color === next.color &&
prev.onSelect === next.onSelect
);
});
You rarely need this, and it is easy to get wrong. If the function ignores a prop, the component keeps showing that prop's old value. Ignoring a function prop is the worst case: the skipped component keeps calling the old function, which reads old state. Compare every prop, and do not do deep equality checks on large data, because the comparison then costs more than the render it saves. Stable props through useMemo and useCallback are almost always the better fix.
Checking that memo works
The quickest check is the one used on this page: a console.log at the top of the component. If it prints when you expected a skip, log the props too, and compare them with the previous ones to find the one that changed. In a real project, React DevTools helps further: turn on "Highlight updates when components render" in its settings to see which components flash on each interaction, and the Profiler tab records every render and, with the matching setting on, why each component rendered.
Two things look like memo failing but are not:
- The first render always happens.
memoonly skips renders after the component has rendered once with those props. - Development builds may render more. Under StrictMode, React renders each component twice in development to catch impure code. The preview here runs like a production build, so each log line above means one real render.
When to use memo
memo is worth it when a component is slow to render and its parent often renders with the same props for it: a long list or a chart next to an input, a sidebar inside a page that updates every second. For components that render quickly, or whose props change every time anyway, it adds a comparison and buys nothing. Before memoizing, see whether you can restructure instead, by moving state down or passing JSX as children, as shown on the rendering page.
React Compiler 1.0, if your project uses it, memoizes components and their props automatically at build time, which makes most manual memo calls unnecessary. Without it, memo works exactly as described here and remains common in React code.
Frequently Asked Questions
What does React.memo do?
It returns a memoized version of a component. When the parent renders, React compares the new props with the previous ones and skips rendering the component if every prop is equal by Object.is.
Why does my memo component still re-render?
Usually because a prop is a new object, array or function created during the parent's render, so it is never equal to the previous one. The component also renders when its own state changes or when a context it reads changes, memo or not.
What is the difference between React.memo and useMemo?
memo wraps a component and skips its render. useMemo is a hook inside a component that caches a computed value. They often work together: useMemo keeps an object prop stable so memo can skip.
Should I wrap every component in memo?
No. Most renders are cheap, and the prop comparison has its own cost. Use it for components that are slow to render and often receive the same props.
Does memo work with children?
Rarely. JSX passed as children is a new object on every render, so a memo component that receives children almost always re-renders.