Skip to main content

What is React.PureComponent

React.PureComponent is a base class for React class components that skips re-renders by running a shallow equality check on props and state before each render.

Theory

TL;DR

  • Think of it like a label check: PureComponent looks at the tag on the box (the reference), not what's inside.
  • Main difference from React.Component: PureComponent has a built-in shouldComponentUpdate that compares props and state shallowly.
  • Flat props (numbers, strings, stable object references) work great. Nested mutations will be missed.
  • For functional components, use React.memo instead.

Quick example

tsx
import React, { PureComponent, Component } from 'react'; class PureChild extends PureComponent<{ count: number }> { render() { console.log('PureChild renders'); return <div>Count: {this.props.count}</div>; } } class RegularChild extends Component<{ count: number }> { render() { console.log('RegularChild renders'); return <div>Count: {this.props.count}</div>; } } class Parent extends Component { state = { count: 0 }; render() { return ( <> <PureChild count={this.state.count} /> <RegularChild count={this.state.count} /> {/* count stays 0, but parent still calls setState */} <button onClick={() => this.setState({ count: this.state.count })}> No change </button> </> ); } } // Click the button: only "RegularChild renders" logs. PureChild skips.

Both receive the same count after the click. RegularChild renders anyway. PureChild checks, finds nothing changed, and bails out.

Key difference

React.Component re-renders every time the parent updates or setState is called, regardless of whether data actually changed. React.PureComponent overrides shouldComponentUpdate with a shallow comparison: it iterates over each prop and state key and checks values with Object.is. If nothing changed, React skips the render and the entire subtree diff. The shallow check adds a small CPU cost per render, but that is almost always cheaper than diffing a whole subtree.

When to use

  • Props are primitives (numbers, strings, booleans): PureComponent gives free optimization with no extra code.
  • Object props are stable references (memoized with useMemo or created once outside render): the shallow check passes correctly.
  • Lists with 50-100+ items where the parent re-renders often but individual items rarely change: PureComponent can cut redundant renders significantly.
  • Nested objects mutate in place: skip PureComponent. It will miss the change and show stale UI.
  • Functional component: use React.memo - same idea, works with hooks.

Comparison table

FeatureReact.ComponentReact.PureComponent
Re-render triggerAny parent update or setStateParent update + actual prop/state change
shouldComponentUpdateNot set by defaultBuilt-in shallow comparison
Handles in-place mutationsYes (always re-renders)No (misses deep changes)
Works well withAny props shapeFlat or stable-reference props
Functional equivalentN/AReact.memo

How it works internally

PureComponent sets a flag (isPureReactComponent = true) that React's reconciler reads. Before calling render, React runs the built-in shouldComponentUpdate, which uses Object.is to compare each key in props and state. If all comparisons match, the component and its entire subtree skip reconciliation. In React 16+ with fiber, this bail-out is efficient: React skips the diff, skips render, and moves on to the next work unit.

Common mistakes

Mutating objects or arrays in place:

tsx
// Wrong: same array reference, PureComponent skips the re-render this.props.items[0].done = true; this.forceUpdate(); // a hack, and still won't help PureComponent children // Right: create a new reference this.setState({ items: this.state.items.map((item, i) => i === 0 ? { ...item, done: true } : item ) });

PureComponent sees the same array reference and skips. The UI stays stale. This is the most common production bug with PureComponent.

Passing a new object literal on every render:

tsx
// Wrong: new object each render defeats the shallow check entirely <PureChild config={{ theme: 'dark' }} /> // Right: stable reference const config = useMemo(() => ({ theme: 'dark' }), []); <PureChild config={config} />

You get the overhead of the shallow check with zero benefit. PureComponent re-renders on every cycle.

Assuming it handles nested data changes:

If you spread the top-level object but mutate a nested property in place, behavior becomes unpredictable depending on whether the top-level reference changes. Always copy nested objects too: { ...user, details: { ...user.details, age: 31 } }. Anything less is a bug waiting to surface.

Real-world usage

  • Large data tables and lists: wrap each row in PureComponent so only changed rows re-render when the parent updates.
  • Material-UI TableRow and Ant Design table rows use PureComponent internally for exactly this reason.
  • Redux-connected lists: combine PureComponent with reselect selectors so components re-render only when their slice of state changes.
  • React DevTools Profiler: add PureComponent to a bottleneck component and check "why did this render?" to confirm skips are happening.

I find PureComponent most useful in list scenarios. Add 100 items, toggle one, and you can watch 99 components skip their render in the Profiler without any guesswork.

Follow-up questions

Q: What is the difference between shallow and deep equality?
A: Shallow checks each key's value with Object.is, one level deep. Deep equality recurses into nested values. Deep is expensive and rarely needed if you keep state immutable.

Q: Can you override shouldComponentUpdate on a PureComponent?
A: Yes. Your override replaces the built-in shallow check entirely. Do this only if you need custom comparison logic, like ignoring certain props.

Q: What is the functional component equivalent of PureComponent?
A: React.memo. It wraps a function component and does the same shallow prop comparison. Pass a custom comparator as the second argument if you need more control.

Q: Does PureComponent affect error boundaries?
A: No. If render throws, the error still bubbles up to the nearest error boundary regardless of whether the shallow check ran.

Q: In React 18 concurrent mode, does PureComponent help with async rendering?
A: PureComponent's check is synchronous. For async rendering benefits, combine React.memo with useDeferredValue. PureComponent alone does not interact with concurrent features.

Examples

List row optimization

You have 100+ todo items. The parent re-renders on any state change, but each row should only re-render if its own data changed.

tsx
interface Todo { id: number; text: string; done: boolean; } class TodoItem extends React.PureComponent<{ todo: Todo }> { render() { const { todo } = this.props; console.log(`Rendering todo #${todo.id}`); return ( <li style={{ textDecoration: todo.done ? 'line-through' : 'none' }}> {todo.text} </li> ); } } // Parent re-renders the list on every change. // Toggle todo #3 -> only "Rendering todo #3" logs. The other 99 skip.

Each TodoItem receives a todo object from an immutable update (a new object only for the changed item, same references for the rest). PureComponent catches the unchanged references and skips those renders.

The mutation trap

This is the edge case that causes stale UI in production:

tsx
class UserCard extends React.PureComponent<{ user: { name: string; age: number } }> { render() { console.log('UserCard renders'); return <div>{this.props.user.name}, age {this.props.user.age}</div>; } } class App extends React.Component { state = { user: { name: 'Alice', age: 30 } }; updateAge = () => { // Wrong: mutate the existing object, then setState with the same reference this.state.user.age = 31; this.setState({ user: this.state.user }); // same reference! // PureComponent skips - UI shows age 30, actual value is 31 }; render() { return ( <> <UserCard user={this.state.user} /> <button onClick={this.updateAge}>Birthday</button> </> ); } } // Fix: this.setState({ user: { ...this.state.user, age: 31 } })

The shallow check sees the same user reference and skips the render. The UI shows the old age. The fix is one line: spread the object to create a new reference.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?