React Intermediate

Performance Optimization in React

CodingerWeb
CodingerWeb
21 views 60 min read

React Performance Optimization

Learn techniques to optimize React applications for better performance, including memoization, lazy loading, and efficient rendering strategies.

React.memo for Component Memoization

// Expensive component that should only re-render when props change
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
    console.log('ExpensiveComponent rendered');
    
    return (
        

{data.title}

{data.description}

); }); // Custom comparison function const ExpensiveComponentWithCustomComparison = React.memo( ({ data, onUpdate }) => { return (

{data.title}

{data.description}

); }, (prevProps, nextProps) => { // Return true if props are equal (skip re-render) return prevProps.data.id === nextProps.data.id && prevProps.data.title === nextProps.data.title; } );

useMemo for Expensive Calculations

function ProductList({ products, searchTerm, sortBy }) {
    // Expensive filtering and sorting operation
    const filteredAndSortedProducts = useMemo(() => {
        console.log('Filtering and sorting products...');
        
        let filtered = products.filter(product =>
            product.name.toLowerCase().includes(searchTerm.toLowerCase())
        );

        return filtered.sort((a, b) => {
            if (sortBy === 'price') {
                return a.price - b.price;
            } else if (sortBy === 'name') {
                return a.name.localeCompare(b.name);
            }
            return 0;
        });
    }, [products, searchTerm, sortBy]);

    return (
        
{filteredAndSortedProducts.map(product => ( ))}
); }

useCallback for Function Memoization

function TodoApp() {
    const [todos, setTodos] = useState([]);
    const [filter, setFilter] = useState('all');

    // Without useCallback, this function is recreated on every render
    const handleToggleTodo = useCallback((id) => {
        setTodos(prevTodos =>
            prevTodos.map(todo =>
                todo.id === id ? { ...todo, completed: !todo.completed } : todo
            )
        );
    }, []); // Empty dependency array since we use functional update

    const handleDeleteTodo = useCallback((id) => {
        setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
    }, []);

    const filteredTodos = useMemo(() => {
        switch (filter) {
            case 'active':
                return todos.filter(todo => !todo.completed);
            case 'completed':
                return todos.filter(todo => todo.completed);
            default:
                return todos;
        }
    }, [todos, filter]);

    return (
        
); }

Lazy Loading with React.lazy and Suspense

// Lazy load components
const Dashboard = React.lazy(() => import('./components/Dashboard'));
const Profile = React.lazy(() => import('./components/Profile'));
const Settings = React.lazy(() => import('./components/Settings'));

function App() {
    return (
        
            
Loading...
}> } /> } /> } />
); }

Virtualization for Large Lists

// Using react-window for virtualization
import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style, data }) => (
    

{data[index].name}

{data[index].description}

); function VirtualizedList({ items }) { return ( {Row} ); }

Optimizing Context Updates

// Split contexts to minimize re-renders
const UserContext = createContext();
const UserActionsContext = createContext();

function UserProvider({ children }) {
    const [user, setUser] = useState(null);

    // Memoize actions to prevent unnecessary re-renders
    const actions = useMemo(() => ({
        login: (userData) => setUser(userData),
        logout: () => setUser(null),
        updateProfile: (updates) => setUser(prev => ({ ...prev, ...updates }))
    }), []);

    return (
        
            
                {children}
            
        
    );
}

Debouncing User Input

// Custom hook for debouncing
function useDebounce(value, delay) {
    const [debouncedValue, setDebouncedValue] = useState(value);

    useEffect(() => {
        const handler = setTimeout(() => {
            setDebouncedValue(value);
        }, delay);

        return () => {
            clearTimeout(handler);
        };
    }, [value, delay]);

    return debouncedValue;
}

// Usage in search component
function SearchComponent() {
    const [searchTerm, setSearchTerm] = useState('');
    const debouncedSearchTerm = useDebounce(searchTerm, 300);

    useEffect(() => {
        if (debouncedSearchTerm) {
            // Perform search
            searchAPI(debouncedSearchTerm);
        }
    }, [debouncedSearchTerm]);

    return (
         setSearchTerm(e.target.value)}
            placeholder="Search..."
        />
    );
}

Bundle Splitting and Code Splitting

// Dynamic imports for code splitting
const loadDashboard = () => import('./Dashboard');
const loadAnalytics = () => import('./Analytics');

// Route-based code splitting
function App() {
    return (
        
            }>
                        
                    
                } 
            />
        
    );
}

Performance Monitoring

// Using React DevTools Profiler API
import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
    console.log('Component:', id);
    console.log('Phase:', phase);
    console.log('Actual duration:', actualDuration);
}

function App() {
    return (
        
            
); }

Practical Exercise

Optimize a slow React application by:

  • Identifying performance bottlenecks
  • Implementing memoization where appropriate
  • Adding lazy loading for routes
  • Optimizing list rendering

Performance Best Practices

  • Use React DevTools Profiler to identify bottlenecks
  • Don't optimize prematurely - measure first
  • Memoize expensive calculations and functions
  • Implement lazy loading for large components
  • Use virtualization for long lists
  • Optimize images and assets
  • Minimize bundle size with code splitting