React Performance Optimization: Advanced Techniques

React applications can become sluggish as they grow. Let’s explore advanced performance optimization techniques that will keep your React apps fast and responsive, even with complex state and large datasets.

Understanding React’s Rendering Behavior

Component Re-rendering Patterns

// Problem: Unnecessary re-renders
const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  // This object is recreated on every render
  const userConfig = {
    theme: 'dark',
    language: 'en'
  };
  
  return (
    <div>
      <ExpensiveChild config={userConfig} />
      <Counter count={count} setCount={setCount} />
      <NameInput name={name} setName={setName} />
    </div>
  );
};

// Solution: Memoize expensive components
const ExpensiveChild = React.memo(({ config }) => {
  console.log('ExpensiveChild rendered');
  return <div>Expensive operations here...</div>;
});

const OptimizedParent = () => {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  // Memoize the config object
  const userConfig = useMemo(() => ({
    theme: 'dark',
    language: 'en'
  }), []);
  
  return (
    <div>
      <ExpensiveChild config={userConfig} />
      <Counter count={count} setCount={setCount} />
      <NameInput name={name} setName={setName} />
    </div>
  );
};

Advanced Memoization Techniques

Custom Comparison Functions

const UserCard = React.memo(({ user, onUpdate }) => {
  return (
    <div className="user-card">
      <img src={user.avatar} alt={user.name} />
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <button onClick={() => onUpdate(user.id)}>Update</button>
    </div>
  );
}, (prevProps, nextProps) => {
  // Custom comparison - only re-render if user data changed
  return (
    prevProps.user.id === nextProps.user.id &&
    prevProps.user.name === nextProps.user.name &&
    prevProps.user.email === nextProps.user.email &&
    prevProps.user.avatar === nextProps.user.avatar
  );
});

Optimizing Context Usage

// Problem: All consumers re-render when any context value changes
const AppContext = createContext();

const AppProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  const [notifications, setNotifications] = useState([]);
  
  const value = {
    user, setUser,
    theme, setTheme,
    notifications, setNotifications
  };
  
  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
};

// Solution: Split contexts by concern
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();

const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  
  const value = useMemo(() => ({
    user, setUser
  }), [user]);
  
  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
};

// Custom hooks for each context
const useUser = () => {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within UserProvider');
  }
  return context;
};

Code Splitting and Lazy Loading

Route-Based Code Splitting

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
const Settings = lazy(() => import('./pages/Settings'));

// Loading component
const PageLoader = () => (
  <div className="flex items-center justify-center min-h-screen">
    <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-500"></div>
  </div>
);

const App = () => {
  return (
    <BrowserRouter>
      <Suspense fallback={<PageLoader />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/profile" element={<Profile />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
};

Component-Based Code Splitting

// Lazy load heavy components
const ChartComponent = lazy(() => 
  import('./Chart').then(module => ({
    default: module.Chart
  }))
);

const DataVisualization = ({ data }) => {
  const [showChart, setShowChart] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowChart(true)}>
        Show Chart
      </button>
      
      {showChart && (
        <Suspense fallback={<div>Loading chart...</div>}>
          <ChartComponent data={data} />
        </Suspense>
      )}
    </div>
  );
};

Virtual Scrolling for Large Lists

Custom Virtual List Implementation

import { useState, useEffect, useMemo } from 'react';

const VirtualList = ({ 
  items, 
  itemHeight = 50, 
  containerHeight = 400,
  renderItem 
}) => {
  const [scrollTop, setScrollTop] = useState(0);
  
  const visibleCount = Math.ceil(containerHeight / itemHeight);
  const totalHeight = items.length * itemHeight;
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(startIndex + visibleCount + 1, items.length);
  
  const visibleItems = useMemo(() => 
    items.slice(startIndex, endIndex)
  , [items, startIndex, endIndex]);
  
  const handleScroll = (e) => {
    setScrollTop(e.target.scrollTop);
  };
  
  return (
    <div 
      style={{ height: containerHeight, overflow: 'auto' }}
      onScroll={handleScroll}
    >
      <div style={{ height: totalHeight, position: 'relative' }}>
        {visibleItems.map((item, index) => (
          <div
            key={startIndex + index}
            style={{
              position: 'absolute',
              top: (startIndex + index) * itemHeight,
              height: itemHeight,
              width: '100%'
            }}
          >
            {renderItem(item, startIndex + index)}
          </div>
        ))}
      </div>
    </div>
  );
};

// Usage
const UserList = ({ users }) => {
  return (
    <VirtualList
      items={users}
      itemHeight={60}
      containerHeight={500}
      renderItem={(user, index) => (
        <div className="flex items-center p-3 border-b">
          <img 
            src={user.avatar} 
            alt={user.name}
            className="w-10 h-10 rounded-full mr-3"
          />
          <div>
            <h4 className="font-medium">{user.name}</h4>
            <p className="text-sm text-gray-600">{user.email}</p>
          </div>
        </div>
      )}
    />
  );
};

State Management Optimization

Optimizing useState and useReducer

// Problem: Complex state updates causing re-renders
const UserProfile = () => {
  const [user, setUser] = useState({
    name: '',
    email: '',
    preferences: {
      theme: 'light',
      notifications: true,
      language: 'en'
    }
  });
  
  // This causes full re-render even for small changes
  const updatePreference = (key, value) => {
    setUser(prev => ({
      ...prev,
      preferences: {
        ...prev.preferences,
        [key]: value
      }
    }));
  };
  
  return (
    <div>
      <UserInfo user={user} />
      <UserPreferences 
        preferences={user.preferences}
        onUpdate={updatePreference}
      />
    </div>
  );
};

// Solution: Split state by concern
const OptimizedUserProfile = () => {
  const [userInfo, setUserInfo] = useState({
    name: '',
    email: ''
  });
  
  const [preferences, setPreferences] = useState({
    theme: 'light',
    notifications: true,
    language: 'en'
  });
  
  const updatePreference = useCallback((key, value) => {
    setPreferences(prev => ({
      ...prev,
      [key]: value
    }));
  }, []);
  
  return (
    <div>
      <UserInfo user={userInfo} />
      <UserPreferences 
        preferences={preferences}
        onUpdate={updatePreference}
      />
    </div>
  );
};

Custom Hooks for Performance

// Custom hook for debounced values
const useDebounce = (value, delay) => {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);
  
  return debouncedValue;
};

// Custom hook for intersection observer
const useIntersectionObserver = (ref, options = {}) => {
  const [isIntersecting, setIsIntersecting] = useState(false);
  
  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      setIsIntersecting(entry.isIntersecting);
    }, options);
    
    if (ref.current) {
      observer.observe(ref.current);
    }
    
    return () => {
      observer.disconnect();
    };
  }, [ref, options]);
  
  return isIntersecting;
};

// Usage: Lazy load images
const LazyImage = ({ src, alt, ...props }) => {
  const imgRef = useRef();
  const isVisible = useIntersectionObserver(imgRef, {
    threshold: 0.1
  });
  
  return (
    <div ref={imgRef} {...props}>
      {isVisible && <img src={src} alt={alt} />}
    </div>
  );
};

Bundle Optimization

Webpack Bundle Analyzer

# Install bundle analyzer
npm install --save-dev webpack-bundle-analyzer

# Add to package.json scripts
"analyze": "npm run build && npx webpack-bundle-analyzer build/static/js/*.js"

Tree Shaking Optimization

// Bad: Imports entire library
import _ from 'lodash';
import * as utils from './utils';

// Good: Import only what you need
import { debounce, throttle } from 'lodash';
import { formatDate, validateEmail } from './utils';

// Even better: Use babel plugin for automatic optimization
// babel-plugin-import or babel-plugin-lodash

Performance Monitoring

Custom Performance Hook

const usePerformanceMonitor = (componentName) => {
  useEffect(() => {
    const startTime = performance.now();
    
    return () => {
      const endTime = performance.now();
      const renderTime = endTime - startTime;
      
      if (renderTime > 16) { // More than one frame at 60fps
        console.warn(
          `Slow render detected in ${componentName}: ${renderTime.toFixed(2)}ms`
        );
      }
    };
  });
};

// Usage
const ExpensiveComponent = () => {
  usePerformanceMonitor('ExpensiveComponent');
  
  // Component logic here
  return <div>...</div>;
};

React DevTools Profiler Integration

import { Profiler } from 'react';

const onRenderCallback = (id, phase, actualDuration) => {
  console.log('Component:', id);
  console.log('Phase:', phase);
  console.log('Duration:', actualDuration);
};

const App = () => {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <Router>
        <Routes>
          {/* Your routes */}
        </Routes>
      </Router>
    </Profiler>
  );
};

React performance optimization is about understanding when and why components re-render, then applying the right techniques to minimize unnecessary work. Profile first, optimize second, and always measure the impact of your changes.