React.memo
Learn to use React.memo for preventing unnecessary re-renders of components
60 minā¢By Priygop Teamā¢Last updated: Feb 2026
What is React.memo?
React.memo is a higher-order component that memoizes your component. It prevents re-rendering if the props haven't changed, which can improve performance.
Basic React.memo Usage
Example
import React from 'react';
// Without React.memo - re-renders on every parent update
function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent rendered'); // This will log on every parent update
return (
<div>
<h2>Expensive Component</h2>
<p>{data}</p>
</div>
);
}
// With React.memo - only re-renders when props change
const MemoizedComponent = React.memo(function ExpensiveComponent({ data }) {
console.log('MemoizedComponent rendered'); // This will only log when data changes
return (
<div>
<h2>Memoized Component</h2>
<p>{data}</p>
</div>
);
});
// Parent component
function ParentComponent() {
const [count, setCount] = useState(0);
const [data, setData] = useState('Hello World');
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<MemoizedComponent data={data} />
</div>
);
}
// Real-world example: Product List
const ProductCard = React.memo(function ProductCard({ product, onAddToCart }) {
console.log(`ProductCard ${product.id} rendered`);
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.price}</p>
<button onClick={() => onAddToCart(product)}>
Add to Cart
</button>
</div>
);
});
function ProductList({ products, onAddToCart }) {
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState('name');
return (
<div>
<div className="controls">
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Filter products..."
/>
<select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
<option value="name">Sort by Name</option>
<option value="price">Sort by Price</option>
</select>
</div>
<div className="products-grid">
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={onAddToCart}
/>
))}
</div>
</div>
);
}Custom Comparison Function
Example
// Custom comparison function for React.memo
const UserProfile = React.memo(function UserProfile({ user, onUpdate }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<button onClick={() => onUpdate(user.id)}>
Update User
</button>
</div>
);
}, (prevProps, nextProps) => {
// Custom comparison - only re-render if user data actually changed
return (
prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name &&
prevProps.user.email === nextProps.user.email
);
});
// Using the component
function UserList() {
const [users, setUsers] = useState([]);
const handleUpdate = useCallback((userId) => {
// Update logic
}, []);
return (
<div>
{users.map(user => (
<UserProfile
key={user.id}
user={user}
onUpdate={handleUpdate}
/>
))}
</div>
);
}Practice Exercise: Performance Optimization
Example
// Exercise: Optimize a Data Visualization Dashboard
// Create a performance-optimized dashboard with charts and data tables
// src/components/DataTable.js
import React, { useMemo } from 'react';
const DataTable = React.memo(function DataTable({ data, columns, sortBy, sortDirection }) {
const sortedData = useMemo(() => {
if (!sortBy) return data;
return [...data].sort((a, b) => {
const aValue = a[sortBy];
const bValue = b[sortBy];
if (sortDirection === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
}, [data, sortBy, sortDirection]);
return (
<table className="data-table">
<thead>
<tr>
{columns.map(column => (
<th key={column.key}>{column.label}</th>
))}
</tr>
</thead>
<tbody>
{sortedData.map(row => (
<tr key={row.id}>
{columns.map(column => (
<td key={column.key}>{row[column.key]}</td>
))}
</tr>
))}
</tbody>
</table>
);
});
// src/components/Chart.js
const Chart = React.memo(function Chart({ data, type, width, height }) {
const chartData = useMemo(() => {
// Expensive chart data processing
return data.map(item => ({
label: item.label,
value: item.value,
percentage: (item.value / data.reduce((sum, d) => sum + d.value, 0)) * 100
}));
}, [data]);
return (
<div className="chart" style={{ width, height }}>
<h3>{type} Chart</h3>
<svg width={width} height={height}>
{chartData.map((item, index) => (
<rect
key={index}
x={index * (width / chartData.length)}
y={height - (item.percentage / 100) * height}
width={width / chartData.length - 2}
height={(item.percentage / 100) * height}
fill="#007bff"
/>
))}
</svg>
</div>
);
});
// src/components/Dashboard.js
import React, { useState, useCallback, useMemo } from 'react';
function Dashboard() {
const [data, setData] = useState([]);
const [filters, setFilters] = useState({});
const [sortConfig, setSortConfig] = useState({ sortBy: null, direction: 'asc' });
const [selectedChart, setSelectedChart] = useState('bar');
// Memoized filtered data
const filteredData = useMemo(() => {
return data.filter(item => {
return Object.entries(filters).every(([key, value]) => {
if (!value) return true;
return item[key].toLowerCase().includes(value.toLowerCase());
});
});
}, [data, filters]);
// Memoized chart data
const chartData = useMemo(() => {
return filteredData.reduce((acc, item) => {
const category = item.category;
if (!acc[category]) {
acc[category] = { label: category, value: 0 };
}
acc[category].value += item.value;
return acc;
}, {});
}, [filteredData]);
// Memoized callbacks
const handleSort = useCallback((column) => {
setSortConfig(prev => ({
sortBy: column,
direction: prev.sortBy === column && prev.direction === 'asc' ? 'desc' : 'asc'
}));
}, []);
const handleFilter = useCallback((key, value) => {
setFilters(prev => ({
...prev,
[key]: value
}));
}, []);
const columns = [
{ key: 'name', label: 'Name' },
{ key: 'category', label: 'Category' },
{ key: 'value', label: 'Value' },
{ key: 'date', label: 'Date' }
];
return (
<div className="dashboard">
<div className="controls">
<input
placeholder="Filter by name..."
onChange={(e) => handleFilter('name', e.target.value)}
/>
<select onChange={(e) => handleFilter('category', e.target.value)}>
<option value="">All Categories</option>
<option value="sales">Sales</option>
<option value="marketing">Marketing</option>
<option value="development">Development</option>
</select>
<select value={selectedChart} onChange={(e) => setSelectedChart(e.target.value)}>
<option value="bar">Bar Chart</option>
<option value="line">Line Chart</option>
<option value="pie">Pie Chart</option>
</select>
</div>
<div className="dashboard-content">
<div className="charts-section">
<Chart
data={Object.values(chartData)}
type={selectedChart}
width={400}
height={300}
/>
</div>
<div className="table-section">
<DataTable
data={filteredData}
columns={columns}
sortBy={sortConfig.sortBy}
sortDirection={sortConfig.direction}
/>
</div>
</div>
</div>
);
}
// Performance monitoring
function PerformanceMonitor() {
const [renderCount, setRenderCount] = useState(0);
useEffect(() => {
setRenderCount(prev => prev + 1);
});
return (
<div className="performance-monitor">
<p>Component rendered {renderCount} times</p>
</div>
);
}
// Challenge: Add virtualization to the data table
// Challenge: Implement data caching with React Query
// Challenge: Add real-time data updates with WebSocket