Skip to main content
โšก Calmops

Performance Optimization in React

Performance Optimization in React

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() { }
    
  2. Use lazy loading for routes:

    // โœ… Good: Lazy load
    const Dashboard = lazy(() => import('./Dashboard'));
    
    // โŒ Bad: Import everything
    import Dashboard from './Dashboard';
    
  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

Comments