Pure Components are React components that automatically implement shallow comparison of props and state for performance optimization. They re-render only when input data changes.
✅ Key characteristics:
❌ Limitations:
Key rule: Use pure components only with immutable data and when properly managing state.
Pure components are a special type of React components that automatically optimize performance by implementing shallow comparison of props and state. They re-render only when input data changes.
Pure components implement the shouldComponentUpdate
method with shallow comparison of props and state:
// Regular component - always re-renders
class RegularComponent extends Component {
render() {
console.log('RegularComponent renders');
return <div>{this.props.value}</div>;
}
}
// Pure component - re-renders only when props/state change
class PureComponentExample extends PureComponent {
render() {
console.log('PureComponent renders');
return <div>{this.props.value}</div>;
}
}
// Usage
function App() {
const [count, setCount] = useState(0);
// This data doesn't change
const data = { name: 'Alexander' };
return (
<div>
<button onClick={() => setCount(count + 1)}>
Increase: {count}
</button>
{/* Regular component will re-render on every App render */}
<RegularComponent value={data.name} />
{/* Pure component re-renders only if data.name changes */}
<PureComponentExample value={data.name} />
</div>
);
}
Pure components use shallow comparison of props and state:
// Example of shallow comparison
function shallowEqual(objA, objB) {
if (objA === objB) {
return true;
}
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Compare only first-level properties
for (let i = 0; i < keysA.length; i++) {
if (!objB.hasOwnProperty(keysA[i]) ||
objA[keysA[i]] !== objB[keysA[i]]) {
return false;
}
}
return true;
}
// Regular component
class RegularCounter extends Component {
render() {
return (
<div>
<p>Counter: {this.props.count}</p>
<button onClick={this.props.onIncrement}>
Increase
</button>
</div>
);
}
}
// Pure component
class PureCounter extends PureComponent {
render() {
return (
<div>
<p>Counter: {this.props.count}</p>
<button onClick={this.props.onIncrement}>
Increase
</button>
</div>
);
}
}
// Demonstrating the difference
function App() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState('');
// Object that doesn't change
const config = { theme: 'light' };
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<input
value={otherState}
onChange={(e) => setOtherState(e.target.value)}
placeholder="Other state"
/>
{/* RegularCounter will re-render when otherState changes */}
<RegularCounter count={count} onIncrement={handleIncrement} />
{/* PureCounter re-renders only when count changes */}
<PureCounter count={count} onIncrement={handleIncrement} />
</div>
);
}
With hooks, functional components can use memoization:
import { memo, useState } from 'react';
// Regular functional component
function RegularComponent({ name, age }) {
console.log('RegularComponent renders');
return (
<div>
<h1>{name}</h1>
<p>Age: {age}</p>
</div>
);
}
// Memoized component (PureComponent equivalent)
const MemoizedComponent = memo(function MemoizedComponent({ name, age }) {
console.log('MemoizedComponent renders');
return (
<div>
<h1>{name}</h1>
<p>Age: {age}</p>
</div>
);
});
// With custom comparison function
const CustomMemoizedComponent = memo(
function CustomMemoizedComponent({ user }) {
console.log('CustomMemoizedComponent renders');
return (
<div>
<h1>{user.name}</h1>
<p>Age: {user.age}</p>
</div>
);
},
// Custom comparison function
(prevProps, nextProps) => {
return prevProps.user.name === nextProps.user.name &&
prevProps.user.age === nextProps.user.age;
}
);
// Usage
function App() {
const [count, setCount] = useState(0);
// This data doesn't change between renders
const userData = { name: 'Alexander', age: 25 };
return (
<div>
<button onClick={() => setCount(count + 1)}>
Counter: {count}
</button>
<RegularComponent name={userData.name} age={userData.age} />
<MemoizedComponent name={userData.name} age={userData.age} />
<CustomMemoizedComponent user={userData} />
</div>
);
}
// 1. Components with primitive props
function UserInfo({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
const PureComponentUserInfo = memo(UserInfo);
// 2. Components with immutable objects
function TodoItem({ todo, onToggle, onDelete }) {
return (
<div className={todo.completed ? 'completed' : ''}>
<span>{todo.text}</span>
<button onClick={() => onToggle(todo.id)}>Toggle</button>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</div>
);
}
const PureComponentTodoItem = memo(TodoItem);
// 3. Components with arrays (if array is recreated when changed)
function TodoList({ todos, onToggle, onDelete }) {
return (
<div>
{todos.map(todo => (
<PureComponentTodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</div>
);
}
// 1. Components with mutating objects
class BadExample extends PureComponent {
render() {
// ❌ If parentData mutates, component won't re-render
return <div>{this.props.parentData.value}</div>;
}
}
// 2. Components with functions created in parent
function Parent() {
const [count, setCount] = useState(0);
// ❌ New function on every render
const handleClick = () => {
console.log('Clicked!');
};
return (
<div>
<button onClick={() => setCount(count + 1)}>
Counter: {count}
</button>
{/* Component will re-render due to new function */}
<PureComponentWithFunction onClick={handleClick} />
</div>
);
}
// ✅ Correct approach - memoize function
function GoodParent() {
const [count, setCount] = useState(0);
// ✅ Function created once
const handleClick = useCallback(() => {
console.log('Clicked!');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Counter: {count}
</button>
{/* Component won't re-render */}
<PureComponentWithFunction onClick={handleClick} />
</div>
);
}
import { memo, useState, useCallback } from 'react';
// Pure component for list item
const ListItem = memo(function ListItem({ item, onUpdate, onDelete }) {
console.log(`ListItem ${item.id} renders`);
return (
<div className="list-item">
<span>{item.text}</span>
<button onClick={() => onUpdate(item.id, `${item.text} (updated)`)}>
Update
</button>
<button onClick={() => onDelete(item.id)}>
Delete
</button>
</div>
);
});
// List component
const List = memo(function List({ items, onUpdate, onDelete }) {
console.log('List renders');
return (
<div>
{items.map(item => (
<ListItem
key={item.id}
item={item}
onUpdate={onUpdate}
onDelete={onDelete}
/>
))}
</div>
);
});
// Parent component
function App() {
const [items, setItems] = useState([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
]);
const [otherState, setOtherState] = useState('');
// Memoized functions
const handleUpdate = useCallback((id, newText) => {
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, text: newText } : item
)
);
}, []);
const handleDelete = useCallback((id) => {
setItems(prevItems =>
prevItems.filter(item => item.id !== id)
);
}, []);
return (
<div>
<input
value={otherState}
onChange={(e) => setOtherState(e.target.value)}
placeholder="Other state"
/>
{/* List re-renders only when items change */}
<List
items={items}
onUpdate={handleUpdate}
onDelete={handleDelete}
/>
</div>
);
}
import { memo, useState } from 'react';
// ❌ Problem with nested objects
const UserProfileBad = memo(function UserProfileBad({ user }) {
return (
<div>
<h2>{user.profile.name}</h2>
<p>Age: {user.profile.age}</p>
<p>Email: {user.contact.email}</p>
</div>
);
});
// ✅ Correct approach - destructuring or custom comparison
const UserProfileGood = memo(
function UserProfileGood({ user }) {
return (
<div>
<h2>{user.profile.name}</h2>
<p>Age: {user.profile.age}</p>
<p>Email: {user.contact.email}</p>
</div>
);
},
// Custom comparison function for nested objects
(prevProps, nextProps) => {
return (
prevProps.user.profile.name === nextProps.user.profile.name &&
prevProps.user.profile.age === nextProps.user.profile.age &&
prevProps.user.contact.email === nextProps.user.contact.email
);
}
);
// Alternative approach - passing individual props
const UserProfileBest = memo(function UserProfileBest({ name, age, email }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
});
// ❌ Incorrect
class BadParent extends Component {
constructor(props) {
super(props);
this.state = {
data: { value: 0 }
};
}
handleClick = () => {
// Mutating existing object
this.state.data.value += 1;
this.setState({ data: this.state.data }); // ❌ Not creating new object!
};
render() {
return (
<div>
<button onClick={this.handleClick}>
Increase
</button>
{/* PureComponent won't re-render because it's the same object */}
<PureChild data={this.state.data} />
</div>
);
}
}
// ✅ Correct
class GoodParent extends Component {
constructor(props) {
super(props);
this.state = {
data: { value: 0 }
};
}
handleClick = () => {
// Creating new object
this.setState({
data: { value: this.state.data.value + 1 } // ✅ New object
});
};
render() {
return (
<div>
<button onClick={this.handleClick}>
Increase
</button>
{/* PureComponent will re-render because it's a new object */}
<PureChild data={this.state.data} />
</div>
);
}
}
// ❌ Incorrect
function BadParent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Counter: {count}
</button>
{/* New function on every render - PureComponent will re-render */}
<PureComponent onClick={() => console.log('Click!')} />
</div>
);
}
// ✅ Correct
function GoodParent() {
const [count, setCount] = useState(0);
// Function created once
const handleClick = useCallback(() => {
console.log('Click!');
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Counter: {count}
</button>
{/* Same function on every render - PureComponent won't re-render */}
<PureComponent onClick={handleClick} />
</div>
);
}
Pure components are React components that automatically optimize performance through shallow comparison of props and state:
✅ Advantages:
❌ Limitations:
Key points:
memo
Understanding pure components helps create more performant React applications and avoid unnecessary re-renders.
Want more articles for interview preparation? Subscribe to EasyAdvice, bookmark the site, and improve every day 💪