React Pure Components
Pure components are a performance optimization technique in React that helps prevent unnecessary re-renders by implementing shallow comparison of props and state.
Table of Contents
- React Pure Components
What are Pure Components?
Pure components are React components that only re-render when their props or state actually change. They implement a shallow comparison to determine if a re-render is necessary.
Characteristics of Pure Components:
- Shallow Comparison: Compare props and state using shallow equality
- Performance Optimization: Prevent unnecessary re-renders
- Predictable: Only re-render when data actually changes
- Immutable Data: Work best with immutable data patterns
When to Use Pure Components:
- Components that receive the same props frequently
- Components that are expensive to render
- Components in lists or grids
- Components that don’t need frequent updates
React.memo (Functional Components)
React.memo is a higher-order component that memoizes functional components, preventing re-renders when props haven’t changed.
Basic Usage:
import React from 'react';
// Regular functional component
function UserCard({ user, onDelete }) {
console.log('UserCard rendered');
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onDelete(user.id)}>Delete</button>
</div>
);
}
// Memoized version
const MemoizedUserCard = React.memo(UserCard);
// Usage
function UserList({ users, onDeleteUser }) {
return (
<div>
{users.map(user => (
<MemoizedUserCard
key={user.id}
user={user}
onDelete={onDeleteUser}
/>
))}
</div>
);
}
Custom Comparison Function:
import React from 'react';
function ExpensiveComponent({ data, config }) {
// Expensive rendering logic
return (
<div>
{/* Complex rendering */}
</div>
);
}
// Custom comparison function
const areEqual = (prevProps, nextProps) => {
// Only re-render if data changed, ignore config changes
return prevProps.data === nextProps.data;
};
const MemoizedExpensiveComponent = React.memo(ExpensiveComponent, areEqual);
// Usage
function App() {
const [data, setData] = useState([]);
const [config, setConfig] = useState({ theme: 'light' });
return (
<div>
<MemoizedExpensiveComponent data={data} config={config} />
<button onClick={() => setConfig({ theme: 'dark' })}>
Change Theme
</button>
</div>
);
}
With Inline Functions:
import React, { useCallback } from 'react';
function UserCard({ user, onDelete }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<button onClick={() => onDelete(user.id)}>Delete</button>
</div>
);
}
const MemoizedUserCard = React.memo(UserCard);
// Parent component
function UserList({ users }) {
// Bad: Inline function creates new reference on every render
const handleDelete = (id) => {
console.log('Deleting user:', id);
};
return (
<div>
{users.map(user => (
<MemoizedUserCard
key={user.id}
user={user}
onDelete={handleDelete} // New function reference every time
/>
))}
</div>
);
}
// Good: Use useCallback to memoize the function
function UserList({ users }) {
const handleDelete = useCallback((id) => {
console.log('Deleting user:', id);
}, []); // Empty dependency array since it doesn't depend on any values
return (
<div>
{users.map(user => (
<MemoizedUserCard
key={user.id}
user={user}
onDelete={handleDelete} // Same function reference
/>
))}
</div>
);
}
PureComponent (Class Components)
PureComponent is the class component equivalent of React.memo. It implements shouldComponentUpdate with shallow comparison.
Basic Usage:
import React from 'react';
// Regular class component
class UserCard extends React.Component {
render() {
console.log('UserCard rendered');
const { user, onDelete } = this.props;
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onDelete(user.id)}>Delete</button>
</div>
);
}
}
// Pure component version
class PureUserCard extends React.PureComponent {
render() {
console.log('PureUserCard rendered');
const { user, onDelete } = this.props;
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onDelete(user.id)}>Delete</button>
</div>
);
}
}
// Usage
class UserList extends React.Component {
render() {
const { users, onDeleteUser } = this.props;
return (
<div>
{users.map(user => (
<PureUserCard
key={user.id}
user={user}
onDelete={onDeleteUser}
/>
))}
</div>
);
}
}
Custom shouldComponentUpdate:
class ExpensiveComponent extends React.PureComponent {
shouldComponentUpdate(nextProps, nextState) {
// Custom comparison logic
const { data, config } = this.props;
const { data: nextData, config: nextConfig } = nextProps;
// Only re-render if data changed, ignore config changes
return data !== nextData;
}
render() {
const { data, config } = this.props;
return (
<div>
{/* Expensive rendering based on data */}
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
}
Performance Benefits
Before Pure Components:
function ParentComponent() {
const [count, setCount] = useState(0);
const users = [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' }
];
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment Count
</button>
{/* These components re-render every time count changes */}
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
After Pure Components:
function ParentComponent() {
const [count, setCount] = useState(0);
const users = [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' }
];
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment Count
</button>
{/* These components only re-render when their props change */}
{users.map(user => (
<MemoizedUserCard key={user.id} user={user} />
))}
</div>
);
}
const MemoizedUserCard = React.memo(UserCard);
Performance Measurement:
import React, { useState, useEffect } from 'react';
function PerformanceTest() {
const [count, setCount] = useState(0);
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
setRenderCount(prev => prev + 1);
});
return (
<div>
<p>Parent rendered: {renderCount} times</p>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Update Count
</button>
<MemoizedExpensiveComponent data={{ value: 'static' }} />
</div>
);
}
const MemoizedExpensiveComponent = React.memo(ExpensiveComponent);
function ExpensiveComponent({ data }) {
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
setRenderCount(prev => prev + 1);
});
// Simulate expensive operation
const expensiveCalculation = () => {
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.random();
}
return result;
};
return (
<div>
<p>ExpensiveComponent rendered: {renderCount} times</p>
<p>Expensive calculation result: {expensiveCalculation()}</p>
</div>
);
}
Shallow Comparison
How Shallow Comparison Works:
// Shallow comparison checks if references are the same
const shallowEqual = (obj1, obj2) => {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || obj1 === null ||
typeof obj2 !== 'object' || obj2 === null) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]) {
return false;
}
}
return true;
};
Examples of Shallow Comparison:
// These will be considered equal (same reference)
const obj1 = { name: 'John', age: 30 };
const obj2 = obj1;
shallowEqual(obj1, obj2); // true
// These will be considered different (different references)
const obj1 = { name: 'John', age: 30 };
const obj2 = { name: 'John', age: 30 };
shallowEqual(obj1, obj2); // false
// Arrays with same content but different references
const arr1 = [1, 2, 3];
const arr2 = [1, 2, 3];
shallowEqual(arr1, arr2); // false
// Same array reference
const arr1 = [1, 2, 3];
const arr2 = arr1;
shallowEqual(arr1, arr2); // true
Common Pitfalls:
// Bad: Creating new objects/arrays in render
function ParentComponent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Update Count
</button>
{/* New object created on every render */}
<MemoizedChild data={{ value: 'static' }} />
{/* New array created on every render */}
<MemoizedList items={[1, 2, 3]} />
</div>
);
}
// Good: Use useMemo for expensive objects/arrays
function ParentComponent() {
const [count, setCount] = useState(0);
const staticData = useMemo(() => ({ value: 'static' }), []);
const staticItems = useMemo(() => [1, 2, 3], []);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Update Count
</button>
<MemoizedChild data={staticData} />
<MemoizedList items={staticItems} />
</div>
);
}
Best Practices
1. Use Pure Components for Expensive Rendering:
// Good: Memoize expensive components
const ExpensiveChart = React.memo(({ data, config }) => {
// Expensive chart rendering
return (
<div className="chart">
{/* Complex chart visualization */}
</div>
);
});
// Usage
function Dashboard({ metrics }) {
const [theme, setTheme] = useState('light');
return (
<div>
<ExpensiveChart data={metrics} config={{ theme }} />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
}
2. Memoize Callback Functions:
// Good: Use useCallback for event handlers
function UserList({ users, onDeleteUser }) {
const handleDelete = useCallback((id) => {
onDeleteUser(id);
}, [onDeleteUser]);
return (
<div>
{users.map(user => (
<MemoizedUserCard
key={user.id}
user={user}
onDelete={handleDelete}
/>
))}
</div>
);
}
3. Memoize Expensive Calculations:
// Good: Use useMemo for expensive calculations
function DataTable({ data, filters }) {
const filteredData = useMemo(() => {
return data.filter(item => {
// Expensive filtering logic
return filters.every(filter => filter.test(item));
});
}, [data, filters]);
return (
<table>
{filteredData.map(item => (
<MemoizedTableRow key={item.id} item={item} />
))}
</table>
);
}
const MemoizedTableRow = React.memo(TableRow);
4. Avoid Pure Components for Simple Components:
// Unnecessary: Simple component doesn't benefit from memoization
const SimpleText = React.memo(({ text }) => <p>{text}</p>);
// Better: Keep it simple
const SimpleText = ({ text }) => <p>{text}</p>;
5. Use Keys Properly:
// Good: Stable keys for list items
function UserList({ users }) {
return (
<div>
{users.map(user => (
<MemoizedUserCard
key={user.id} // Stable key
user={user}
/>
))}
</div>
);
}
// Bad: Unstable keys
function UserList({ users }) {
return (
<div>
{users.map((user, index) => (
<MemoizedUserCard
key={index} // Unstable key
user={user}
/>
))}
</div>
);
}
Common Interview Questions
Q: What is a pure component in React?
- A pure component is a component that only re-renders when its props or state actually change, using shallow comparison.
Q: What is the difference between React.memo and PureComponent?
- React.memo is for functional components, while PureComponent is for class components. Both implement shallow comparison.
Q: How does shallow comparison work?
- Shallow comparison checks if the references of props and state are the same, not their content.
Q: When should you use pure components?
- Use pure components for expensive components, components that receive the same props frequently, or components in lists.
Q: What are the limitations of pure components?
- They only do shallow comparison, so they won’t detect changes in nested objects or arrays with the same reference.
Q: How do you handle callback functions with pure components?
- Use useCallback to memoize callback functions so they maintain the same reference between renders.
Q: Can you override the comparison logic in pure components?
- Yes, React.memo accepts a custom comparison function as the second argument.
Q: What is the performance impact of pure components?
- Pure components can significantly improve performance by preventing unnecessary re-renders, especially for expensive components.
Q: When should you NOT use pure components?
- For simple components where the overhead of comparison exceeds the benefit, or when props change frequently.
Summary
- Pure components prevent unnecessary re-renders using shallow comparison
- React.memo is for functional components, PureComponent is for class components
- Shallow comparison checks reference equality, not deep content equality
- Use pure components for expensive rendering, list items, and components with stable props
- useCallback and useMemo help maintain stable references for pure components
- Pure components work best with immutable data patterns
- Understanding when and how to use pure components is crucial for React performance optimization
Interview angle
- “What is a pure component?” - one that renders the same output for the same props and state, with no side effects during render.
React.PureComponentandReact.memoadd a shallow props comparison to skip re-rendering when nothing changed. - “Why does
React.memoso often fail to help?” - shallow comparison. An inline object, array or arrow function prop is a new reference every render, so the comparison always fails. That is whatuseMemo/useCallbackwere for on the parent side. - “Does any of this still matter with React Compiler?” - much less. The compiler memoizes automatically, so hand-written
memo/useMemo/useCallbackis largely redundant in new code and adds noise. Purity is still required - the compiler assumes it, and mutating props or state during render produces genuinely wrong output. See react_compiler.md.