React Performance Optimization Techniques

January 5, 2025
4 min read
Jason Sy
ReactPerformanceOptimizationJavaScript

React Performance Optimization Techniques

Performance is crucial for user experience. Here are essential techniques to optimize your React applications.

1. Use React.memo for Component Memoization

Prevent unnecessary re-renders:

import { memo } from 'react';

interface UserCardProps {
  name: string;
  email: string;
}

const UserCard = memo(({ name, email }: UserCardProps) => {
  console.log('UserCard rendered');
  return (
    <div>
      <h3>{name}</h3>
      <p>{email}</p>
    </div>
  );
});

2. Optimize useMemo and useCallback

Cache expensive computations and functions:

import { useMemo, useCallback } from 'react';

function DataList({ items, filter }) {
  // Memoize expensive filtering operation
  const filteredItems = useMemo(() => {
    return items.filter(item => item.includes(filter));
  }, [items, filter]);

  // Memoize callback to prevent child re-renders
  const handleClick = useCallback((id: string) => {
    console.log('Clicked:', id);
  }, []);

  return (
    <ul>
      {filteredItems.map(item => (
        <ListItem key={item.id} onClick={handleClick} />
      ))}
    </ul>
  );
}

3. Code Splitting with Lazy Loading

Split your bundle for faster initial loads:

import { lazy, Suspense } from 'react';

const HeavyChart = lazy(() => import('@/components/HeavyChart'));
const AdminPanel = lazy(() => import('@/components/AdminPanel'));

function Dashboard() {
  return (
    <div>
      <Suspense fallback={<div>Loading chart...</div>}>
        <HeavyChart />
      </Suspense>
      
      <Suspense fallback={<div>Loading admin panel...</div>}>
        <AdminPanel />
      </Suspense>
    </div>
  );
}

4. Virtual Scrolling for Large Lists

Use virtualization for rendering large datasets:

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50,
  });

  return (
    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px` }}>
        {virtualizer.getVirtualItems().map(virtualRow => (
          <div
            key={virtualRow.index}
            style={{
              position: 'absolute',
              top: 0,
              transform: `translateY(${virtualRow.start}px)`,
            }}
          >
            {items[virtualRow.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

5. Optimize Context Usage

Prevent context re-renders:

import { createContext, useContext, useMemo } from 'react';

const UserContext = createContext(null);

function UserProvider({ children, user }) {
  // Memoize context value
  const value = useMemo(() => ({
    user,
    isLoggedIn: !!user,
  }), [user]);

  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
}

6. Use Transition for Non-Urgent Updates

Prioritize urgent updates:

import { useState, useTransition } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (value: string) => {
    // Urgent: update input immediately
    setQuery(value);
    
    // Non-urgent: search can wait
    startTransition(() => {
      const filtered = performExpensiveSearch(value);
      setResults(filtered);
    });
  };

  return (
    <div>
      <input value={query} onChange={e => handleSearch(e.target.value)} />
      {isPending ? <Spinner /> : <ResultsList items={results} />}
    </div>
  );
}

7. Debounce Expensive Operations

Limit how often expensive functions run:

import { useState, useEffect } from 'react';

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;
}

// Usage
function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      // API call only happens after user stops typing for 500ms
      fetchSearchResults(debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);
}

8. Optimize Image Loading

Use proper image optimization techniques:

import Image from 'next/image';

function OptimizedImage() {
  return (
    <Image
      src="/large-image.jpg"
      alt="Description"
      width={800}
      height={600}
      priority // For above-the-fold images
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,..."
    />
  );
}

Performance Monitoring

Monitor your app's performance:

import { useEffect } from 'react';

function PerformanceMonitor() {
  useEffect(() => {
    // Web Vitals
    if (typeof window !== 'undefined' && 'performance' in window) {
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          console.log('Performance metric:', entry);
        }
      });

      observer.observe({ entryTypes: ['navigation', 'paint', 'measure'] });

      return () => observer.disconnect();
    }
  }, []);
}

Best Practices Checklist

  • ✅ Profile before optimizing
  • ✅ Use React DevTools Profiler
  • ✅ Implement code splitting
  • ✅ Memoize expensive computations
  • ✅ Virtualize long lists
  • ✅ Optimize images and assets
  • ✅ Use proper caching strategies
  • ✅ Monitor performance metrics

Conclusion

React performance optimization is about making smart choices:

  1. Measure first: Use profiling tools to identify bottlenecks
  2. Optimize strategically: Focus on user-facing performance issues
  3. Test thoroughly: Ensure optimizations don't break functionality

Remember: premature optimization is the root of all evil. Always measure before optimizing!

Resources