Module 5: Performance Optimization

Learn React performance optimization techniques including memoization, code splitting, and best practices for building fast applications.

Back to Course|5.5 hours|Advanced

Performance Optimization

Learn React performance optimization techniques including memoization, code splitting, and best practices for building fast applications.

Progress: 0/4 topics completed0%

Select Topics Overview

React.memo

Learn to use React.memo for preventing unnecessary re-renders of components

Content by: Sejal Mishra

MERN Stack Developer

Connect

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

Code 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>
    );
}
Swipe to see more code

Custom Comparison Function

Code 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>
    );
}
Swipe to see more code

Practice Exercise: Performance Optimization

Code 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
Swipe to see more code

🎯 Practice Exercise

Test your understanding of this topic:

Additional Resources

📚 Recommended Reading

  • React Performance Documentation
  • React DevTools Profiler Guide
  • Code Splitting Best Practices

🌐 Online Resources

  • React Performance Tutorial
  • React.memo Examples
  • Code Splitting Guide

Ready for the Next Module?

Continue your learning journey and master the next set of concepts.

Continue to Module 6