skip to content

In React, how does React.memo relate to a class component's shouldComponentUpdate and to React.PureComponent, and what can the class APIs do that memo cannot?

level: middleimportance: should knowfreq 54%

answer

  1. one is the class-era ancestor of the other
  2. shallow compare, one level deep
  3. props only versus props and state
  4. the two comparators return opposite meanings
  5. new object literals defeat both

basics

~20 s

React.memo is the function-component counterpart of PureComponent: it shallow-compares props and skips the re-render if they match. PureComponent also shallow-compares state, and shouldComponentUpdate can compare anything — memo sees props only, and its comparator's return value is inverted.

solid answer

~50 s

`React.memo(Component)` wraps a function component and shallow-compares the incoming props against the previous ones; if they all match by `Object.is`, React reuses the last render. `React.PureComponent` does the same shallow comparison but over **both** props and state, and `shouldComponentUpdate(nextProps, nextState)` is fully manual — you can compare whatever you like, including state and derived values. Two differences trip people up. First, the return convention is inverted: `shouldComponentUpdate` returns `true` to render, while memo's optional second argument `arePropsEqual` returns `true` to say the props are equal and therefore **skip** the render. Second, memo has no view of state or context, so a memoized component still re-renders when its own state updates or a context it consumes changes. And memo is defeated entirely if the parent passes a freshly created object, array or inline function each render.

code

jsx · 9 lines
jsx
import { memo } from 'react';

function Row({ item }) {
  return <li>{item.label}</li>;
}

// Returning true means "props are equal", so React SKIPS the re-render.
// shouldComponentUpdate used the opposite convention: true meant "do render".
export default memo(Row, (prevProps, nextProps) => prevProps.item.id === nextProps.item.id);

go deeper

for a junior

Know that PureComponent and React.memo both do a shallow props comparison to skip work, and that shouldComponentUpdate is the manual class-era version of the same idea.

for a middle

Explain the comparison precisely: one level deep, Object.is per key, props only for memo versus props and state for PureComponent, and the inverted meaning of the two comparators' return values.

for a senior

In review, catch the memo wrapper that can never help because the parent passes a fresh object or inline handler, and be able to say that memo never suppresses re-renders caused by the component's own state or by context.

for a principal

Frame the policy: with the React Compiler able to insert memoization for function components that follow the Rules of React, hand-written memo layers become maintenance debt, and a hot class component is one that the compiler cannot help at all.

## The three APIs `shouldComponentUpdate(nextProps, nextState)` is the raw hook React has always exposed: return `true` and React re-renders the component, return `false` and it skips both the render and the subtree beneath it. `React.PureComponent` is a base class that implements `shouldComponentUpdate` for you as a **shallow** comparison of props and state — for each key, `Object.is(prev[key], next[key])`. It compares one level deep only; it does not walk into nested objects. `React.memo(Component, arePropsEqual?)` is the function-component equivalent. It wraps a component and shallow-compares props; when they match, React reuses the previous render output. ```jsx const Row = React.memo(function Row({ item }) { return <li>{item.label}</li>; }); ``` ## The inverted return value This is the detail interviewers probe, because a mistake here silently inverts the behaviour rather than crashing: - `shouldComponentUpdate` answers *should I render?* — `true` means render. - `arePropsEqual` answers *are these props equal?* — `true` means equal, therefore **skip** the render. A developer who ports a `shouldComponentUpdate` body straight into memo's second argument produces a component that re-renders exactly when it should not, and never re-renders when it should. ## What memo does not cover **State.** PureComponent's shallow compare includes `this.state`, and `shouldComponentUpdate` receives `nextState`. `memo` only ever sees props. A memoized component whose own `useState` setter is called re-renders normally — that is not a bug, it is the boundary of what memo claims. **Context.** A component that reads a context re-renders when that context's value changes, memo or not. memo compares props; context is not a prop. **Forced renders.** `shouldComponentUpdate` is not consulted when a class calls `this.forceUpdate()` — the update goes through regardless. **Everything else the class could reason about.** Because `shouldComponentUpdate` is arbitrary code, a class could compare a derived key, ignore a prop it knows is cosmetic, or throttle updates. memo's default is only the shallow prop compare, and a custom comparator can only look at previous and next props. ## The failure mode both share Shallow comparison is only as useful as the stability of what you pass. Given ```jsx <Row item={{ id, label }} onSelect={() => select(id)} /> ``` both the object literal and the arrow function are new values on every parent render, so a shallow compare finds them unequal every time. PureComponent had exactly this problem with inline callbacks in the class era; memo has it in the hook era. The consequence is a component that pays the cost of comparison on every render and never once skips a render. ## What has changed in React 19 Manual memoization is increasingly something the toolchain can do for you: the React Compiler analyses function components at build time and inserts memoization automatically, which removes much of the hand-written `memo`/`useMemo` layer — but only for function components, and only when the component follows the Rules of React. Classes get none of it. That is a genuine, concrete reason to migrate a hot class component rather than a stylistic one. ## Answering it well Say the equivalence in one line (memo is PureComponent for function components, shallow prop compare), then name the two asymmetries that actually cause bugs: memo does not see state or context, and its comparator's `true` means *skip*, the opposite of `shouldComponentUpdate`'s `true`.

  • A class extends React.PureComponent, pushes an item onto this.state.items with push(), then calls setState({ items: this.state.items }). Nothing re-renders. Why?
    PureComponent's shallow compare uses Object.is on each key. Mutating the array in place leaves the same array reference, so previous and next state look identical and the update is skipped. The fix is to produce a new array — `setState({ items: [...this.state.items, next] })`. The same trap applies to memo when a parent mutates and re-passes an object prop.
  • If a component is wrapped in React.memo, does it still re-render when a context it consumes changes?
    Yes. memo compares props, and context is not a prop — a consumer re-renders whenever the context value it reads changes, regardless of memo. The usual mitigations are splitting the context so consumers subscribe to less, or moving the consuming part into a smaller child, not adding another memo layer.
  • Is shouldComponentUpdate consulted for every update to a class component?
    No. React skips it for updates triggered by `this.forceUpdate()`, which is the escape hatch for exactly that reason. It is also treated as a hint rather than a hard guarantee — React may still choose to render — so it should be an optimization, never the mechanism that keeps your UI correct.

saying these in an interview costs you the question

  • React.memo does a deep comparison of props
  • memo stops a component re-rendering when its own state changes
  • memo's comparator returns true to trigger a re-render
  • PureComponent compares props only, like memo
  • Wrapping every component in memo makes the app faster

context