Back to Rules
React95% popularity

React Performance Optimization Patterns

Use React.memo, useMemo, and useCallback correctly to prevent unnecessary re-renders.

Dan AbramovUpdated Jan 25, 2024

Overview

React's rendering model can cause performance issues when components re-render unnecessarily. Understanding and applying the right optimization technique requires knowing why re-renders happen and which optimization addresses which problem. Premature optimization can add complexity without benefit. React.memo is a higher-order component that memoizes a component's output based on its props. If props don't change, the component won't re-render. However, React performs shallow comparison of props, so objects, arrays, and functions created in parent components will always be "different" between renders. memo should wrap components that are expensive to render and receive primitive or stable reference props. useMemo returns a memoized value computed from its dependencies. It's appropriate for expensive calculations that don't need to run on every render. The dependency array is critical—incorrect dependencies cause stale calculations or wasted recomputation. Setting the wrong dependencies is a common source of bugs. useCallback returns a memoized function, which is essentially useMemo for functions. It's necessary when passing callbacks to child components wrapped in React.memo—the function reference must remain stable across renders. However, overusing useCallback can hurt performance by adding memory overhead and the comparison cost itself. Profiling with React DevTools Profiler identifies actual performance bottlenecks before optimizing. The commitment phase shows what actually rendered and how long it took. Optimizations should target components with high render counts and visible latency rather than applying blanket memoization everywhere.

Code Example

.reactrules
import React, { useState, useMemo, useCallback, memo, useEffect } from 'react';

// Expensive calculation helper
function calculateExpensiveValue(items: Item[], filter: string): number {
  console.log('Calculating...');
  return items
    .filter(item => item.name.includes(filter))
    .reduce((sum, item) => sum + item.value, 0);
}

// Memoized child component
const ExpensiveList = memo(function ExpensiveList({
  items,
  onItemClick,
  filter,
}: {
  items: Item[];
  onItemClick: (id: string) => void;
  filter: string;
}) {
  const filteredItems = useMemo(() => {
    return items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()));
  }, [items, filter]);

  return (
    <ul>
      {filteredItems.map(item => (
        <li key={item.id} onClick={() => onItemClick(item.id)}>
          {item.name} - {item.value}
        </li>
      ))}
    </ul>
  );
});

function SearchableList({ items }: { items: Item[] }) {
  const [filter, setFilter] = useState('');
  const [selectedId, setSelectedId] = useState<string | null>(null);

  // Memoize callback to prevent re-render of memoized child
  const handleItemClick = useCallback((id: string) => {
    setSelectedId(prev => prev === id ? null : id);
  }, []);

  // Memoize expensive calculation
  const totalValue = useMemo(
    () => calculateExpensiveValue(items, filter),
    [items, filter]
  );

  // Memoize filter options
  const filterOptions = useMemo(() => {
    return [...new Set(items.map(item => item.category))];
  }, [items]);

  return (
    <div>
      <input
        value={filter}
        onChange={e => setFilter(e.target.value)}
        placeholder="Filter items..."
      />

      <div>Total value: {totalValue}</div>

      <ExpensiveList
        items={items}
        onItemClick={handleItemClick}
        filter={filter}
      />

      <div>Selected: {selectedId || 'None'}</div>
    </div>
  );
}

// Custom hook for debounced value
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

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

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

  return debouncedValue;
}

More React Rules

REACT
94%

React Component Testing with Testing Library

Test behavior over implementation. Use Testing Library queries to interact with components as users would.

testingreacttesting-library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm }...
Jan 28, 2024by Kent C. Dodds
View Rule
REACT
96%

React Server Components Data Fetching Patterns

Master async/await patterns in Server Components for efficient data loading and caching.

reactserver-componentsdata-fetching
import { db } from '@/lib/db';
import { cache } from 'next/cache';
import { Suspense } from 'react';
import { Skeleton } from '@/components/ui/skeleto...
Feb 10, 2024by React Team
View Rule
REACT
96%

React Form Handling with React Hook Form

Build performant forms with React Hook Form using uncontrolled components and validation.

reactformsreact-hook-form
import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import DatePick...
Mar 22, 2024by Beier(Bill) Luo
View Rule