Skip to main content

Performance Optimization in React

Created: May 8, 2026 Larry Qu 6 min read

Performance optimization ensures fast, responsive React applications. This article covers optimization techniques and best practices.

Introduction

Performance optimization provides:

  • Faster rendering
  • Reduced bundle size
  • Better user experience
  • Lower memory usage
  • Improved SEO

Understanding optimization helps you:

  • Identify performance bottlenecks
  • Optimize component rendering
  • Reduce bundle size
  • Improve load times
  • Monitor performance

Memoization

React.memo

import { memo } from 'react';

// ✅ Good: Memoize expensive component
const UserCard = memo(function UserCard({ user, onEdit }) {
  console.log('UserCard rendered');
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <button onClick={() => onEdit(user.id)}>Edit</button>
    </div>
  );
});

// Usage
function UserList({ users }) {
  const [editingId, setEditingId] = React.useState(null);

  return (
    <div>
      {users.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onEdit={setEditingId}
        />
      ))}
    </div>
  );
}

// ✅ Good: Custom comparison
const UserCard = memo(
  function UserCard({ user, onEdit }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    return prevProps.user.id === nextProps.user.id;
  }
);

// ✅ Good: Memoize with useCallback
function Parent() {
  const [count, setCount] = React.useState(0);

  const handleEdit = React.useCallback((userId) => {
    console.log('Edit user:', userId);
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <UserCard user={{ id: 1, name: 'John' }} onEdit={handleEdit} />
    </div>
  );
}

useMemo

import { useMemo } from 'react';

// ✅ Good: Memoize expensive calculations
function DataProcessor({ data }) {
  const processedData = useMemo(() => {
    console.log('Processing data...');
    return data.map(item => ({
      ...item,
      processed: item.value * 2
    }));
  }, [data]);

  return (
    <div>
      {processedData.map(item => (
        <p key={item.id}>{item.processed}</p>
      ))}
    </div>
  );
}

// ✅ Good: Memoize filtered lists
function FilteredList({ items, filter }) {
  const filteredItems = useMemo(() => {
    return items.filter(item => item.name.includes(filter));
  }, [items, filter]);

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

// ✅ Good: Memoize object creation
function Component() {
  const [count, setCount] = React.useState(0);

  const config = useMemo(() => ({
    theme: 'dark',
    size: 'large'
  }), []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent config={config} />
    </div>
  );
}

useCallback

import { useCallback } from 'react';

// ✅ Good: Memoize callback
function SearchUsers() {
  const [query, setQuery] = React.useState('');

  const handleSearch = useCallback((searchTerm) => {
    console.log('Searching for:', searchTerm);
    // Expensive search operation
  }, []);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        onBlur={() => handleSearch(query)}
      />
    </div>
  );
}

// ✅ Good: useCallback with dependencies
function Parent() {
  const [userId, setUserId] = React.useState(1);

  const fetchUser = useCallback(async () => {
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  }, [userId]);

  return (
    <div>
      <button onClick={() => setUserId(userId + 1)}>Next User</button>
      <UserProfile fetchUser={fetchUser} />
    </div>
  );
}

// ✅ Good: Avoid unnecessary callbacks
function Component() {
  // ❌ Bad: Creates new function on every render
  const handleClick = () => {
    console.log('Clicked');
  };

  // ✅ Good: Memoize callback
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []);

  return <button onClick={handleClick}>Click</button>;
}

Code Splitting

Lazy Loading Components

import { lazy, Suspense } from 'react';

// ✅ Good: Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/admin" element={<AdminPanel />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

// ✅ Good: Custom loading component
function LoadingSpinner() {
  return (
    <div className="spinner">
      <p>Loading...</p>
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Suspense>
  );
}

// ✅ Good: Lazy load with error boundary
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <h1>Failed to load component</h1>;
    }
    return this.props.children;
  }
}

function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </ErrorBoundary>
  );
}

Virtual Scrolling

Virtualized Lists

import { FixedSizeList } from 'react-window';

// ✅ Good: Virtual scrolling for large lists
function LargeList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

// ✅ Good: Dynamic size virtual list
import { VariableSizeList } from 'react-window';

function DynamicList({ items }) {
  const listRef = React.useRef();

  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <VariableSizeList
      height={600}
      itemCount={items.length}
      itemSize={() => 35}
      width="100%"
      ref={listRef}
    >
      {Row}
    </VariableSizeList>
  );
}

Image Optimization

Lazy Loading Images

// ✅ Good: Lazy load images
function Image({ src, alt }) {
  return (
    <img
      src={src}
      alt={alt}
      loading="lazy"
    />
  );
}

// ✅ Good: Responsive images
function ResponsiveImage({ src, alt }) {
  return (
    <picture>
      <source media="(max-width: 600px)" srcSet={`${src}-small.jpg`} />
      <source media="(max-width: 1200px)" srcSet={`${src}-medium.jpg`} />
      <img src={`${src}-large.jpg`} alt={alt} />
    </picture>
  );
}

// ✅ Good: Image with placeholder
function ImageWithPlaceholder({ src, alt, placeholder }) {
  const [loaded, setLoaded] = React.useState(false);

  return (
    <div>
      {!loaded && <img src={placeholder} alt="placeholder" />}
      <img
        src={src}
        alt={alt}
        onLoad={() => setLoaded(true)}
        style={{ display: loaded ? 'block' : 'none' }}
      />
    </div>
  );
}

// ✅ Good: WebP with fallback
function ModernImage({ src, alt }) {
  return (
    <picture>
      <source srcSet={`${src}.webp`} type="image/webp" />
      <img src={`${src}.jpg`} alt={alt} />
    </picture>
  );
}

Bundle Analysis

Analyzing Bundle Size

// ✅ Good: Dynamic imports
function App() {
  const [showAdmin, setShowAdmin] = React.useState(false);

  const loadAdmin = async () => {
    const AdminModule = await import('./pages/AdminPanel');
    setShowAdmin(true);
  };

  return (
    <div>
      <button onClick={loadAdmin}>Load Admin</button>
      {showAdmin && <AdminPanel />}
    </div>
  );
}

// ✅ Good: Tree shaking
// In package.json
// "sideEffects": false

// ✅ Good: Remove unused dependencies
// Use webpack-bundle-analyzer
// npm install --save-dev webpack-bundle-analyzer

Performance Monitoring

React Profiler

import { Profiler } from 'react';

// ✅ Good: Profile component
function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}

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

// ✅ Good: Performance monitoring
function usePerformanceMonitor(componentName) {
  React.useEffect(() => {
    const startTime = performance.now();

    return () => {
      const endTime = performance.now();
      console.log(`${componentName} took ${endTime - startTime}ms`);
    };
  }, [componentName]);
}

function Component() {
  usePerformanceMonitor('Component');
  return <div>Component</div>;
}

Best Practices

  1. Memoize expensive components:
    // ✅ Good: Memoize
    const UserCard = memo(UserCardComponent);
    
    // ❌ Bad: No memoization
    function UserCard() { }
    ```javascript
    
  2. Use lazy loading for routes:
    // ✅ Good: Lazy load
    const Dashboard = lazy(() => import('./Dashboard'));
    
    // ❌ Bad: Import everything
    import Dashboard from './Dashboard';
    ```javascript
    
  3. Optimize images:
    // ✅ Good: Lazy load images
    <img loading="lazy" src={src} />
    
    // ❌ Bad: No optimization
    <img src={src} />
    

Summary

Performance optimization is essential. Key takeaways:

  • Use React.memo for expensive components
  • Use useMemo for expensive calculations
  • Use useCallback for callbacks
  • Lazy load routes and components
  • Optimize images
  • Monitor performance
  • Analyze bundle size
  • Use virtual scrolling for large lists

Next Steps

Resources

Comments

Share this article

Scan to read on mobile