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-inshouldComponentUpdatethat compares props and state shallowly. - Flat props (numbers, strings, stable object references) work great. Nested mutations will be missed.
- For functional components, use
React.memoinstead.
Quick example
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
useMemoor 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
| Feature | React.Component | React.PureComponent |
|---|---|---|
| Re-render trigger | Any parent update or setState | Parent update + actual prop/state change |
| shouldComponentUpdate | Not set by default | Built-in shallow comparison |
| Handles in-place mutations | Yes (always re-renders) | No (misses deep changes) |
| Works well with | Any props shape | Flat or stable-reference props |
| Functional equivalent | N/A | React.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:
// 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:
// 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.
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:
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 readyA concise answer to help you respond confidently on this topic during an interview.