Optimizing with React.memo
In this page:
Wrapping a Component in React.memo
React.memo(Component) returns a new component that skips re-rendering (reusing the last rendered output) if its props haven't changed since the last render, based on a shallow comparison. This can save real work for components that render often but usually with the same props.
Note: React.memo is most valuable for components with expensive rendering logic, not simple ones like a <span>{text}</span>.
Warning: React.memo only compares PROPS — it doesn't prevent a component from re-rendering due to its OWN internal state changing.
Example: Wrapping a Component in React.memo
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const ExpensiveItem = React.memo(function ExpensiveItem({ name }) {
console.log("Rendering:", name);
return <li>{name}</li>;
});
function App() {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Unrelated count: {count}</button>
<ExpensiveItem name="Static Item" />
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
Why New Object/Function Props Defeat memo
React.memo's default comparison checks if each prop is === equal to its previous value. An inline object or function passed as a prop (like style={{color: red}} or onClick={() => {}}) creates a BRAND NEW reference on every parent render, so the comparison always sees changed, even if the values look identical.
Note: Wrap object/function props in useMemo/useCallback in the PARENT component if you want React.memo on the child to actually take effect.
Warning: Adding React.memo to a child while still passing it a fresh inline object or arrow function every render provides zero actual benefit — it will still re-render every time.
Example: Why New Object/Function Props Defeat memo
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const Child = React.memo(function Child({ onClick }) {
console.log("Child rendered");
return <button onClick={onClick}>Click</button>;
});
function App() {
const [count, setCount] = React.useState(0);
// New arrow function every render -- defeats React.memo below:
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={() => console.log("clicked")} />
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
When React.memo Is Worth Using
React.memo pays off most clearly for components that render frequently with UNCHANGED props (like a list item in a large list where only one item actually updates), and whose own rendering work is non-trivial. For cheap, simple components, the comparison overhead can outweigh any savings.
Note: Profile first (see the Profiler tutorial) to confirm a component is both re-rendering often AND doing real work, before reaching for React.memo.
Warning: Adding React.memo everywhere by default is a common overcorrection — it adds a comparison cost to every render, which isn't free either.
Example: When React.memo Is Worth Using
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const ListItem = React.memo(function ListItem({ text }) {
return <li>{text}</li>;
});
function App() {
const items = ["Apple", "Banana", "Cherry"];
const [unrelated, setUnrelated] = React.useState(0);
return (
<div>
<button onClick={() => setUnrelated(unrelated + 1)}>Re-render parent: {unrelated}</button>
<ul>{items.map(i => <ListItem key={i} text={i} />)}</ul>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Wrapping every component in React.memo by default, adding comparison overhead without any real re-render savings for cheap components.
- Expecting React.memo to help when the component's props are new objects/functions on every parent render (it still re-renders in that case).
- Forgetting React.memo only does a shallow prop comparison by default — deeply nested changed values inside an unchanged-reference object won't be detected.
- React.memo wraps a component so it skips re-rendering if its props are shallowly equal to the previous render's props.
- It's most useful for components that render often with the same props, and whose own rendering work is non-trivial.
- React.memo alone doesn't help if the props themselves (objects/functions) are new references every render — pair it with useMemo/useCallback in the parent.
- Applying it to every component 'just in case' adds comparison overhead without a matching benefit.
Available since React 16.6 (React.memo introduced).
Chapter Quiz — Complete all 13 topics to unlock
0/13 topics done
Complete these topics first:
- Why Performance Matters
- Optimizing with React.memo
- Avoiding Unnecessary Re-renders
- Using the React Profiler
- Analyzing Bundle Size
- Image Optimization Techniques
- React as a PWA
- Creating a Production Build
- Environment Variables in React
- Deploying to Netlify
- Deploying to Vercel
- Basic CI/CD for React Apps
- SEO Basics for React SPAs