React.memo
Read react_compiler.md first. The React Compiler has been stable since 1.0 (October 2025) and inserts memoization automatically. In a compiler-enabled project you should not be reaching for
React.memo,useMemooruseCallbackby hand — the compiler does it exhaustively and with correct dependency tracking.This file documents the manual APIs, which remain relevant for: codebases not yet on the compiler, components the compiler bails out of (usually due to mutation during render), and interview questions about how memoization works underneath. Treat it as mechanism, not as current guidance.
React.memo is a higher-order component that memoizes your component, preventing unnecessary re-renders when props haven’t changed.
Table of Contents
- React.memo
What is React.memo?
React.memo is a performance optimization technique that prevents components from re-rendering when their props haven’t changed.
Key Concepts:
- Memoization: Caches component output based on props
- Shallow comparison: Compares props using Object.is()
- Performance optimization: Reduces unnecessary re-renders
- Functional components: Works with function components
Problem React.memo Solves:
// Without React.memo: Component re-renders on every parent update
function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent rendered');
return (
<div>
{data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
function Parent() {
const [count, setCount] = useState(0);
const data = [{ id: 1, name: 'Item 1' }]; // Same data every time
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveComponent data={data} /> {/* Re-renders even though data is the same */}
</div>
);
}
// With React.memo: Component only re-renders when props change
const MemoizedComponent = React.memo(ExpensiveComponent);
Memo Syntax
1. Basic React.memo:
import React from 'react';
function MyComponent({ name, age }) {
console.log('MyComponent rendered');
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
</div>
);
}
// Memoize the component
const MemoizedComponent = React.memo(MyComponent);
// Usage
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<MemoizedComponent name="John" age={30} />
</div>
);
}
2. React.memo with Custom Comparison:
function UserProfile({ user, theme }) {
console.log('UserProfile rendered');
return (
<div className={`profile ${theme}`}>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// Custom comparison function
const areEqual = (prevProps, nextProps) => {
// Only re-render if user data changed, ignore theme changes
return (
prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name &&
prevProps.user.email === nextProps.user.email
);
};
const MemoizedUserProfile = React.memo(UserProfile, areEqual);
// Usage
function App() {
const [theme, setTheme] = useState('light');
const [user] = useState({ id: 1, name: 'John', email: 'john@example.com' });
return (
<div>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
<MemoizedUserProfile user={user} theme={theme} />
</div>
);
}
3. React.memo with Inline Functions:
function Button({ onClick, children }) {
console.log('Button rendered');
return (
<button onClick={onClick}>
{children}
</button>
);
}
const MemoizedButton = React.memo(Button);
// Bad: Inline function causes re-renders
function BadParent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<MemoizedButton onClick={() => console.log('clicked')}>
Click me
</MemoizedButton>
</div>
);
}
// Good: Stable function reference
function GoodParent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return (
<div>
<p>Count: {count}</p>
<MemoizedButton onClick={handleClick}>
Click me
</MemoizedButton>
</div>
);
}
Common Use Cases
1. Expensive Components:
// Expensive rendering component
function ExpensiveList({ items }) {
console.log('ExpensiveList rendered');
// Simulate expensive computation
const processedItems = items.map(item => ({
...item,
processed: item.name.toUpperCase() + ' - ' + item.id
}));
return (
<div>
{processedItems.map(item => (
<div key={item.id} className="item">
{item.processed}
</div>
))}
</div>
);
}
const MemoizedExpensiveList = React.memo(ExpensiveList);
// Usage
function App() {
const [count, setCount] = useState(0);
const [items] = useState([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<MemoizedExpensiveList items={items} />
</div>
);
}
2. Pure Components:
// Pure component that only depends on props
function UserCard({ user, onEdit }) {
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.email}</p>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
}
const MemoizedUserCard = React.memo(UserCard);
// Usage in list
function UserList({ users, onEditUser }) {
return (
<div className="user-list">
{users.map(user => (
<MemoizedUserCard
key={user.id}
user={user}
onEdit={onEditUser}
/>
))}
</div>
);
}
3. Form Components:
function FormField({ label, value, onChange, error }) {
return (
<div className="form-field">
<label>{label}</label>
<input
value={value}
onChange={onChange}
className={error ? 'error' : ''}
/>
{error && <span className="error-message">{error}</span>}
</div>
);
}
const MemoizedFormField = React.memo(FormField);
// Usage
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const [errors, setErrors] = useState({});
const handleChange = useCallback((field) => (e) => {
setFormData(prev => ({ ...prev, [field]: e.target.value }));
}, []);
return (
<form>
<MemoizedFormField
label="Name"
value={formData.name}
onChange={handleChange('name')}
error={errors.name}
/>
<MemoizedFormField
label="Email"
value={formData.email}
onChange={handleChange('email')}
error={errors.email}
/>
<MemoizedFormField
label="Message"
value={formData.message}
onChange={handleChange('message')}
error={errors.message}
/>
</form>
);
}
4. Chart Components:
function Chart({ data, config }) {
console.log('Chart rendered with data:', data);
// Expensive chart rendering logic
const processedData = processChartData(data, config);
return (
<div className="chart">
{/* Chart rendering */}
<svg>
{processedData.map((item, index) => (
<rect
key={index}
x={item.x}
y={item.y}
width={item.width}
height={item.height}
fill={item.color}
/>
))}
</svg>
</div>
);
}
const MemoizedChart = React.memo(Chart);
// Usage
function Dashboard() {
const [selectedMetric, setSelectedMetric] = useState('sales');
const [chartData] = useState([
{ month: 'Jan', sales: 100, revenue: 1000 },
{ month: 'Feb', sales: 150, revenue: 1500 }
]);
const config = { width: 400, height: 300 };
return (
<div>
<select value={selectedMetric} onChange={(e) => setSelectedMetric(e.target.value)}>
<option value="sales">Sales</option>
<option value="revenue">Revenue</option>
</select>
<MemoizedChart data={chartData} config={config} />
</div>
);
}
Advanced Memo Patterns
1. React.memo with useMemo:
function DataTable({ data, columns, sortBy }) {
// Memoize expensive data processing
const processedData = useMemo(() => {
console.log('Processing data...');
return data
.sort((a, b) => a[sortBy].localeCompare(b[sortBy]))
.map(item => ({
...item,
formatted: formatItem(item)
}));
}, [data, sortBy]);
return (
<table>
<thead>
<tr>
{columns.map(col => (
<th key={col.key}>{col.label}</th>
))}
</tr>
</thead>
<tbody>
{processedData.map(item => (
<tr key={item.id}>
{columns.map(col => (
<td key={col.key}>{item[col.key]}</td>
))}
</tr>
))}
</tbody>
</table>
);
}
const MemoizedDataTable = React.memo(DataTable);
2. React.memo with Context:
const ThemeContext = React.createContext();
function ThemedComponent({ children }) {
const theme = useContext(ThemeContext);
return (
<div className={`themed-component ${theme}`}>
{children}
</div>
);
}
const MemoizedThemedComponent = React.memo(ThemedComponent);
// Usage
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
<MemoizedThemedComponent>
<p>This component won't re-render when theme changes</p>
</MemoizedThemedComponent>
</ThemeContext.Provider>
);
}
3. React.memo with Custom Hooks:
function useStableCallback(callback) {
const callbackRef = useRef(callback);
callbackRef.current = callback;
return useCallback((...args) => {
return callbackRef.current(...args);
}, []);
}
function UserActions({ user, onEdit, onDelete }) {
const stableOnEdit = useStableCallback(onEdit);
const stableOnDelete = useStableCallback(onDelete);
return (
<div>
<button onClick={() => stableOnEdit(user.id)}>Edit</button>
<button onClick={() => stableOnDelete(user.id)}>Delete</button>
</div>
);
}
const MemoizedUserActions = React.memo(UserActions);
4. React.memo with Conditional Rendering:
function ConditionalComponent({ condition, data, fallback }) {
if (condition) {
return <div>{data}</div>;
}
return fallback;
}
const MemoizedConditionalComponent = React.memo(ConditionalComponent);
// Custom comparison for conditional rendering
const conditionalAreEqual = (prevProps, nextProps) => {
// Only re-render if condition or data changed
return (
prevProps.condition === nextProps.condition &&
prevProps.data === nextProps.data
);
};
const OptimizedConditionalComponent = React.memo(
ConditionalComponent,
conditionalAreEqual
);
Best Practices
1. Use React.memo for Expensive Components:
// Good: Use for expensive components
const ExpensiveChart = React.memo(Chart);
// Bad: Don't use for simple components
const SimpleText = React.memo(({ text }) => <p>{text}</p>);
// Overhead of memoization > benefit
2. Provide Stable Props:
// Good: Stable object references
function GoodParent() {
const [count, setCount] = useState(0);
const user = useMemo(() => ({ id: 1, name: 'John' }), []);
const handleClick = useCallback(() => console.log('clicked'), []);
return (
<MemoizedComponent user={user} onClick={handleClick} />
);
}
// Bad: Unstable references
function BadParent() {
const [count, setCount] = useState(0);
return (
<MemoizedComponent
user={{ id: 1, name: 'John' }} // New object every render
onClick={() => console.log('clicked')} // New function every render
/>
);
}
3. Use Custom Comparison When Needed:
// Good: Custom comparison for complex props
const areEqual = (prevProps, nextProps) => {
return (
prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name
);
};
const MemoizedComponent = React.memo(Component, areEqual);
// Bad: Default shallow comparison for complex objects
const MemoizedComponent = React.memo(Component);
// May not work as expected with nested objects
4. Combine with useMemo and useCallback:
// Good: Combine optimization techniques
function OptimizedParent() {
const [count, setCount] = useState(0);
const expensiveData = useMemo(() => {
return processData(largeDataset);
}, [largeDataset]);
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return (
<MemoizedComponent
data={expensiveData}
onClick={handleClick}
/>
);
}
5. Avoid Over-optimization:
// Good: Only memoize when beneficial
const ExpensiveComponent = React.memo(HeavyChartComponent);
// Bad: Don't memoize everything
const SimpleComponent = React.memo(({ text }) => <span>{text}</span>);
// Unnecessary overhead
Common Interview Questions
Q: What is React.memo?
- A higher-order component that memoizes functional components to prevent unnecessary re-renders when props haven’t changed.
Q: How does React.memo work?
- It performs a shallow comparison of props and only re-renders the component if props have changed.
Q: When should you use React.memo?
- For expensive components that receive stable props and don’t need to re-render frequently.
Q: What’s the difference between React.memo and useMemo?
- React.memo memoizes entire components, while useMemo memoizes values within a component.
Q: How do you provide custom comparison with React.memo?
- Pass a second argument function that returns true if props are equal, false if component should re-render.
Q: What are the limitations of React.memo?
- Only works with functional components, relies on shallow comparison by default, and can add overhead for simple components.
Q: How do you handle function props with React.memo?
- Use useCallback to create stable function references that don’t change on every render.
Q: Can React.memo work with context?
- Yes, but the component will re-render when context values change, regardless of memoization.
Q: What’s the difference between React.memo and PureComponent?
- React.memo is for functional components, PureComponent is for class components.
Q: How do you debug React.memo issues?
- Add console.logs in the component and custom comparison function to see when re-renders occur.
Summary
- React.memo prevents unnecessary re-renders by memoizing components
- Shallow comparison is used by default to compare props
- Custom comparison can be provided for complex prop structures
- Stable props are crucial for React.memo to work effectively
- Performance optimization should be measured before implementing
- Best practices include using with expensive components and stable references
- Combination with useMemo and useCallback provides optimal performance
- Understanding React.memo is essential for React performance optimization
Interview angle
- “What does
React.memodo?” - wraps a component so it skips re-rendering when props are shallowly equal. It compares props only; state and context changes still re-render it. - “Why does it often do nothing?” - inline objects, arrays and functions are new references each render, so the shallow check always fails. Making it work meant memoizing every such prop on the parent - which is how a one-line optimisation spread through a codebase.
- “Should you still use it in 2026?” - with React Compiler 1.0 enabled, no, for new code: the compiler inserts equivalent memoization from analysis, and manual calls are redundant and can hide bugs by masking impure renders. Keep it in codebases without the compiler, and reach for it only after profiling. See react_compiler.md.
- “When is manual memo still justified?” - a genuinely expensive subtree in a codebase without the compiler, or where the compiler bails out because the component is not provably pure. Measure with the Profiler first; unnecessary memo has its own comparison cost and memory footprint.