PureComponent

Skips re-rendering a class component when its props and state are shallowly unchanged.

Signature
class YourComponent extends PureComponent {
  render(): ReactNode
}
  • Added in React 15.3.0

Parameters

ParameterTypeDescription
$propsobjectPassed to the constructor and forwarded to super(props); shallowly compared against the next props on every update.

Errors

Defining your own shouldComponentUpdate on a subclass warns in development, because it overrides the shallow comparison this class exists to provide.

Changelog

  • PHP 15.3.0Introduced as the class equivalent of a shallow-comparing shouldComponentUpdate.
  • PHP 19.0`propTypes` checks were removed from the React package for all components, including this one, and are now silently ignored. `defaultProps` was removed only for function components; class components, including this one, still support it.

Identical to Component in every respect except one: before re-rendering, React shallowly compares the incoming props and state against the current ones and skips render entirely if every key matches by reference. It is exactly a built-in shouldComponentUpdate doing Object.is on each key.

When to reach for it

When a class component is expensive to render, sits inside a parent that re-renders frequently, and receives props that genuinely stay the same across most of those renders — long list rows are the usual case. Measure first; the comparison itself has a cost, and for a cheap component it is a net loss.

What trips people up

Shallow means shallow. An inline object, array or arrow function prop is a new reference on every parent render, so style={{color: 'red'}} or onClick={() => save(id)} defeats the optimisation completely. The same applies to mutating state in place: this.state.items.push(x) followed by setState({items: this.state.items}) leaves the reference unchanged and the update is skipped. Context changes still force a re-render regardless. For function components, the equivalent is memo — which compares props only, since function state already bails out on identical values.

Example

Try it yourself
Loading editor…
Loading React…

When to use it

  • Stop list rows re-rendering when an unrelated part of a large parent's state changes
  • Avoid a costly chart or table redraw whose data has not changed
  • Replace a hand-written shouldComponentUpdate that only did shallow equality

Frequently asked questions

What is the difference between PureComponent and Component?

PureComponent adds an automatic shallow comparison of props and state and skips rendering when nothing changed, whereas Component re-renders whenever its parent does.

Why is my PureComponent still re-rendering?

Almost always an inline object, array or arrow function prop, which is a new reference on every parent render. A context value the component consumes also forces a re-render regardless of props.

What replaces PureComponent in function components?

memo. It performs the same shallow prop comparison, though it does not compare state, since a function component already skips re-rendering when a state setter receives the same value.

More examples

The inline prop that silently defeats it

Both rows look identical on screen, but only the first ever skips a render. The inline object literal in Bad is a fresh reference on every parent render, so the shallow comparison always reports a change.

Example · jsx
import { Component, PureComponent } from 'react';

class Row extends PureComponent {
  render() {
    return <p style={this.props.style}>{this.props.label}</p>;
  }
}

const rowStyle = { fontWeight: 'bold' }; // created once

class Good extends Component {
  render() {
    // Same object every render: the bail-out works
    return <Row label="optimised" style={rowStyle} />;
  }
}

class Bad extends Component {
  render() {
    // New object every render: PureComponent never skips
    return <Row label="not optimised" style={{ fontWeight: 'bold' }} />;
  }
}

export default function App() {
  return (
    <div>
      <Good />
      <Bad />
    </div>
  );
}

The function-component equivalent

memo does the same shallow prop comparison for function components. It does not compare state, because a function component's own setState already bails out when the new value is identical to the current one.

Example · jsx
import { memo, useState } from 'react';

const Row = memo(function Row({ count }) {
  return <p>Row — count is {count}</p>;
});

export default function Demo() {
  const [count, setCount] = useState(0);
  const [unrelated, setUnrelated] = useState(0);

  return (
    <div>
      <Row count={count} />
      <p>Unrelated state: {unrelated}</p>
      <button onClick={() => setCount((c) => c + 1)}>count + 1</button>{' '}
      <button onClick={() => setUnrelated((u) => u + 1)}>unrelated + 1</button>
    </div>
  );
}

Mutation makes the update vanish

The mutating button appears to do nothing — the item is in state but the list never redraws. The copying button works. This is the most common bug report against pure components, and it is a mutation bug, not a React bug.

Example · jsx
import { Component, PureComponent } from 'react';

class ItemList extends PureComponent {
  render() {
    return (
      <ul>
        {this.props.items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    );
  }
}

class App extends Component {
  constructor(props) {
    super(props);
    this.state = { items: ['alpha'] };
    this.addBroken = this.addBroken.bind(this);
    this.addCorrect = this.addCorrect.bind(this);
  }

  addBroken() {
    // Same array reference: the child never re-renders
    this.state.items.push('mutated');
    this.setState({ items: this.state.items });
  }

  addCorrect() {
    // New array: the shallow comparison sees a change
    this.setState((s) => ({ items: [...s.items, 'copied'] }));
  }

  render() {
    return (
      <div>
        <ItemList items={this.state.items} />
        <button onClick={this.addBroken}>add (mutating)</button>{' '}
        <button onClick={this.addCorrect}>add (copying)</button>
      </div>
    );
  }
}

export default App;

Discussion

  • Be the first to comment on this lesson.