React
02 / 07

Performance Optimization

React Performance Optimization

Comprehensive guide to optimizing React applications for better performance, user experience, and scalability:

React.memo() - Preventing Unnecessary Re-renders

React.memo() is a higher-order component that memoizes the result of a component. It only re-renders if its props have changed, helping to prevent unnecessary re-renders.

// Basic memo usage
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
  console.log('Rendering expensive component');
  return (
    <div>
      <h2>{data.title}</h2>
      <p>{data.description}</p>
      <button onClick={onUpdate}>Update</button>
    </div>
  );
});

// Custom comparison function
const CustomMemoComponent = React.memo(
  ({ user, settings }) => {
    return <UserProfile user={user} settings={settings} />;
  },
  (prevProps, nextProps) => {
    // Custom comparison logic
    return prevProps.user.id === nextProps.user.id &&
           prevProps.settings.theme === nextProps.settings.theme;
  }
);

useMemo() - Memoizing Expensive Calculations

useMemo memoizes the result of a computation and only recalculates when dependencies change. It helps optimize expensive calculations and object creation.

function ProductList({ products, filters, sortBy }) {
  // Expensive calculation - only runs when dependencies change
  const filteredAndSortedProducts = useMemo(() => {
    console.log('Filtering and sorting products');
    
    return products
      .filter(product => {
        return filters.category === 'all' || product.category === filters.category;
      })
      .filter(product => product.price >= filters.minPrice)
      .sort((a, b) => {
        if (sortBy === 'price') return a.price - b.price;
        if (sortBy === 'name') return a.name.localeCompare(b.name);
        return 0;
      });
  }, [products, filters, sortBy]);
  
  return (
    <div>
      {filteredAndSortedProducts.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

useCallback() - Memoizing Functions

useCallback memoizes a function and only creates a new function when dependencies change. It helps prevent unnecessary re-renders of child components that depend on the function.

function TodoApp({ todos }) {
  const [filter, setFilter] = useState('all');
  
  // Memoized callback - only changes when todos change
  const handleToggleTodo = useCallback((id) => {
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  }, []);
  
  // Memoized callback with dependencies
  const handleAddTodo = useCallback((text) => {
    const newTodo = {
      id: Date.now(),
      text,
      completed: false,
      createdAt: new Date()
    };
    setTodos(prevTodos => [...prevTodos, newTodo]);
  }, []);
  
  return (
    <div>
      <TodoList 
        todos={todos}
        onToggle={handleToggleTodo}
        onAdd={handleAddTodo}
      />
    </div>
  );
}

Code Splitting with React.lazy()

Code splitting allows you to split your code into smaller chunks that can be loaded on demand, reducing the initial bundle size and improving performance.

import { 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
const PageLoader = () => (
  <div className="flex items-center justify-center h-64">
    <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
    <span className="ml-2">Loading...</span>
  </div>
);

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  );
}

Virtual Scrolling for Large Lists

Virtual scrolling renders only the visible items in a large list, dramatically improving performance for lists with thousands of items.

import { FixedSizeList as List } from 'react-window';

const VirtualizedList = ({ items }) => {
  const Row = ({ index, style }) => (
    <div style={style} className="flex items-center p-4 border-b">
      <div className="w-8 h-8 bg-gray-300 rounded-full mr-4"></div>
      <div>
        <h3 className="font-semibold">{items[index].name}</h3>
        <p className="text-gray-600">{items[index].email}</p>
      </div>
    </div>
  );
  
  return (
    <List
      height={600}
      itemCount={items.length}
      itemSize={80}
      width="100%"
    >
      {Row}
    </List>
  );
};

// Usage
function App() {
  const [users, setUsers] = useState([]);
  
  useEffect(() => {
    // Load 10,000 users
    const loadUsers = async () => {
      const response = await fetch('/api/users?limit=10000');
      const data = await response.json();
      setUsers(data);
    };
    loadUsers();
  }, []);
  
  return <VirtualizedList items={users} />;
}

Bundle Analysis and Optimization

Analyzing your bundle size helps identify optimization opportunities and reduce the initial load time.

# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer

# Analyze bundle size
npm run build
npm run analyze

# Or with webpack-bundle-analyzer
npx webpack-bundle-analyzer build/static/js/*.js

Performance Monitoring

Monitor your React application performance using React DevTools Profiler and real user monitoring tools.

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

function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
  console.log('Profiler:', {
    id,
    phase,
    actualDuration,
    baseDuration,
    startTime,
    commitTime
  });
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <Header />
      <MainContent />
      <Footer />
    </Profiler>
  );
}

// Performance monitoring with Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  // Send to your analytics service
  console.log(metric);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);

Common Performance Anti-patterns

  • Creating objects/arrays in render methods

  • Inline function definitions in JSX

  • Overusing useEffect without proper dependencies

  • Not memoizing expensive calculations

Performance Best Practices

  • Use React.memo() for components that receive the same props frequently

  • Implement code splitting for route-based components

  • Use virtual scrolling for large lists

  • Optimize images with lazy loading and proper formats

  • Monitor and measure performance regularly

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free