← Back to React Course | Chapter 14: Performance & Production Deployment | Lesson 2 of 13

Optimizing with React.memo

React.memo is like telling a component dont bother redrawing yourself if nothing you care about actually changed since last time'.

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

markup
<!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

markup
<!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

markup
<!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>
Common Mistakes
  1. Wrapping every component in React.memo by default, adding comparison overhead without any real re-render savings for cheap components.
  2. 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).
  3. Forgetting React.memo only does a shallow prop comparison by default — deeply nested changed values inside an unchanged-reference object won't be detected.
Chapter Summary
  • 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.
Browser Support

Available since React 16.6 (React.memo introduced).

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.