Learn React performance optimization techniques including memoization, code splitting, and best practices for building fast applications.
Learn React performance optimization techniques including memoization, code splitting, and best practices for building fast applications.
Learn to use React.memo for preventing unnecessary re-renders of components
Content by: Sejal Mishra
MERN Stack Developer
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.
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 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>
);
}
// 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
Test your understanding of this topic:
Master useMemo and useCallback hooks for optimizing expensive calculations and preventing unnecessary re-renders
Content by: Harshit Sangani
MERN Stack Developer
useMemo is a React hook that memoizes the result of a computation. It only recalculates the value when one of its dependencies changes.
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ numbers }) {
// Expensive calculation that only runs when numbers change
const expensiveValue = useMemo(() => {
return numbers.reduce((sum, num) => sum + num, 0) * 1000;
}, [numbers]);
return (
<div>
<h2>Expensive Calculation Result: {expensiveValue}</h2>
<p>Numbers: {numbers.join(', ')}</p>
</div>
);
}
// Filtering and sorting with useMemo
function UserList({ users, searchTerm, sortBy }) {
const filteredAndSortedUsers = useMemo(() => {
let filtered = users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
if (sortBy === 'name') {
filtered.sort((a, b) => a.name.localeCompare(b.name));
} else if (sortBy === 'age') {
filtered.sort((a, b) => a.age - b.age);
}
return filtered;
}, [users, searchTerm, sortBy]);
return (
<div>
{filteredAndSortedUsers.map(user => (
<div key={user.id}>
<h3>{user.name}</h3>
<p>Age: {user.age}</p>
</div>
))}
</div>
);
}
// useCallback for memoizing functions
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const [users, setUsers] = useState([]);
// Memoized callback that only changes when count changes
const handleIncrement = useCallback(() => {
setCount(c => c + 1);
}, []);
// Memoized callback for user operations
const handleAddUser = useCallback((newUser) => {
setUsers(prev => [...prev, newUser]);
}, []);
const handleDeleteUser = useCallback((userId) => {
setUsers(prev => prev.filter(user => user.id !== userId));
}, []);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={handleIncrement}>Increment</button>
<UserList
users={users}
onAddUser={handleAddUser}
onDeleteUser={handleDeleteUser}
/>
</div>
);
}
// Child component that benefits from memoized callbacks
const UserList = React.memo(function UserList({ users, onAddUser, onDeleteUser }) {
return (
<div>
{users.map(user => (
<UserItem
key={user.id}
user={user}
onDelete={onDeleteUser}
/>
))}
</div>
);
});
Test your understanding of this topic:
Learn code splitting techniques to reduce bundle size and improve loading performance
Content by: Himanshu Dabhi
MERN Stack Developer
Code splitting is a technique that allows you to split your code into various bundles which can be loaded on demand or in parallel. It helps reduce the initial bundle size and improve loading performance.
import React, { Suspense, lazy } from 'react';
import { Routes, Route } from 'react-router-dom';
// Lazy load components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Contact = lazy(() => import('./pages/Contact'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
// Loading component
function LoadingSpinner() {
return (
<div className="loading">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}
// App with code splitting
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}
// Conditional lazy loading
function ConditionalComponent({ shouldLoad }) {
const [Component, setComponent] = useState(null);
useEffect(() => {
if (shouldLoad) {
const LazyComponent = lazy(() => import('./HeavyComponent'));
setComponent(() => LazyComponent);
}
}, [shouldLoad]);
if (!Component) return null;
return (
<Suspense fallback={<div>Loading component...</div>}>
<Component />
</Suspense>
);
}
// Dynamic imports with error handling
function DynamicComponent({ componentName }) {
const [Component, setComponent] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const loadComponent = async () => {
try {
const module = await import(`./components/${componentName}`);
setComponent(() => module.default);
} catch (err) {
setError(err.message);
}
};
loadComponent();
}, [componentName]);
if (error) return <div>Error loading component: {error}</div>;
if (!Component) return <div>Loading...</div>;
return <Component />;
}
// Preloading components
function PreloadButton({ componentName, onLoad }) {
const preloadComponent = async () => {
try {
await import(`./components/${componentName}`);
onLoad();
} catch (error) {
}
};
return (
<button onClick={preloadComponent}>
Preload {componentName}
</button>
);
}
Test your understanding of this topic:
Learn advanced performance optimization techniques and best practices for React applications
Content by: Uves Ansari
MERN Stack Developer
import React from 'react';
import { FixedSizeList as List } from 'react-window';
// Virtualized list for large datasets
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
<div className="row">
<span>{items[index].name}</span>
<span>{items[index].email}</span>
</div>
</div>
);
return (
<List
height={400}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</List>
);
}
// Web Worker for heavy computations
// worker.js
self.onmessage = function(e) {
const { data, type } = e.data;
if (type === 'HEAVY_CALCULATION') {
const result = performHeavyCalculation(data);
self.postMessage({ type: 'RESULT', result });
}
};
function performHeavyCalculation(data) {
// Simulate heavy computation
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += Math.sqrt(i);
}
return result;
}
// Using web worker in React
function HeavyCalculationComponent() {
const [result, setResult] = useState(null);
const [loading, setLoading] = useState(false);
const performCalculation = useCallback((data) => {
setLoading(true);
const worker = new Worker('/worker.js');
worker.onmessage = function(e) {
if (e.data.type === 'RESULT') {
setResult(e.data.result);
setLoading(false);
}
};
worker.postMessage({ data, type: 'HEAVY_CALCULATION' });
}, []);
return (
<div>
<button onClick={() => performCalculation([1, 2, 3, 4, 5])}>
Start Heavy Calculation
</button>
{loading && <p>Calculating...</p>}
{result && <p>Result: {result}</p>}
</div>
);
}
// Error Boundary Component
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Log error to service
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-boundary">
<h2>Something went wrong.</h2>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
// Using Error Boundaries
function App() {
return (
<ErrorBoundary>
<Header />
<ErrorBoundary>
<MainContent />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
);
}
Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Continue to Module 6