kritsana.devpassing
runartifactsdocsworkhistorydispatch

© 2026 Kritsana Prasit — built from source on a self-hosted runner

resume.pdfrssdispatch

docs / React

React Performance Optimization Techniques

Learn advanced techniques to optimize your React applications for better performance and user experience

5 min read

React Performance Optimization Techniques

Performance optimization is crucial for creating responsive and efficient React applications. This guide covers advanced techniques to identify and fix performance bottlenecks in your React apps.

Measuring Performance

Before optimizing, you need to measure performance:

React DevTools Profiler

The React DevTools Profiler is an essential tool for identifying performance issues:

jsx
// No code needed - just use the React DevTools browser extension
// and click on the "Profiler" tab

Performance Monitoring

You can also use the built-in Performance API:

jsx
import React, { useEffect } from 'react';
 
function PerformanceMonitor() {
  useEffect(() => {
    // Measure time to first render
    const paintMetrics = performance.getEntriesByType('paint');
    const firstPaint = paintMetrics.find(({ name }) => name === 'first-paint');
    const firstContentfulPaint = paintMetrics.find(({ name }) => name === 'first-contentful-paint');
 
    console.log(`First Paint: ${firstPaint?.startTime}ms`);
    console.log(`First Contentful Paint: ${firstContentfulPaint?.startTime}ms`);
  }, []);
 
  return null;
}

Memoization Techniques

React.memo

Prevent unnecessary re-renders with React.memo:

jsx
import React from 'react';
 
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  // Expensive rendering logic here
  return (
    <div>
      {data.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
});
 
// Only re-renders if props change

useMemo Hook

Cache expensive calculations:

jsx
import React, { useMemo } from 'react';
 
function DataProcessor({ data, filter }) {
  // This calculation will only run when data or filter changes
  const processedData = useMemo(() => {
    console.log('Processing data...');
    return data
      .filter(item => item.name.includes(filter))
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [data, filter]);
 
  return (
    <div>
      {processedData.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

useCallback Hook

Memoize functions to prevent unnecessary re-renders of child components:

jsx
import React, { useState, useCallback } from 'react';
 
function ParentComponent() {
  const [count, setCount] = useState(0);
 
  // This function is memoized and only changes when dependencies change
  const handleClick = useCallback(() => {
    console.log('Button clicked');
    setCount(c => c + 1);
  }, []);
 
  return (
    <div>
      <p>Count: {count}</p>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}
 
const ChildComponent = React.memo(function ChildComponent({ onClick }) {
  console.log('ChildComponent rendered');
  return <button onClick={onClick}>Click me</button>;
});

Code Splitting

Split your code into smaller chunks that load on demand:

React.lazy and Suspense

jsx
import React, { Suspense, lazy } from 'react';
 
// Import component lazily
const HeavyComponent = lazy(() => import('./HeavyComponent'));
 
function App() {
  return (
    <div>
      <h1>My App</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}

Route-based Code Splitting

jsx
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
 
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const Contact = lazy(() => import('./routes/Contact'));
 
function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

Virtual Lists

For long lists, use virtualization:

jsx
import React from 'react';
import { FixedSizeList } from 'react-window';
 
function VirtualizedList({ items }) {
  const Row = ({ index, style }) => <div style={style}>Item {items[index].name}</div>;
 
  return (
    <FixedSizeList height={500} width="100%" itemCount={items.length} itemSize={35}>
      {Row}
    </FixedSizeList>
  );
}

Avoiding Unnecessary Renders

Using PureComponent or shouldComponentUpdate

For class components:

jsx
import React, { PureComponent } from 'react';
 
class OptimizedComponent extends PureComponent {
  // PureComponent implements shouldComponentUpdate with a shallow prop and state comparison
  render() {
    return <div>{this.props.value}</div>;
  }
}
 
// Or manually implement shouldComponentUpdate
class ManuallyOptimizedComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return this.props.value !== nextProps.value;
  }
 
  render() {
    return <div>{this.props.value}</div>;
  }
}

Debouncing and Throttling

For input handlers and other frequently firing events:

jsx
import React, { useState, useCallback } from 'react';
import debounce from 'lodash/debounce';
 
function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState([]);
 
  // Create a debounced search function
  const debouncedSearch = useCallback(
    debounce(term => {
      console.log('Searching for:', term);
      // Perform search operation
      fetch(`/api/search?q=${term}`)
        .then(res => res.json())
        .then(data => setResults(data));
    }, 500),
    []
  );
 
  const handleChange = e => {
    const value = e.target.value;
    setSearchTerm(value);
    debouncedSearch(value);
  };
 
  return (
    <div>
      <input type="text" value={searchTerm} onChange={handleChange} placeholder="Search..." />
      <ul>
        {results.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

Web Workers

Offload heavy computations to a web worker:

jsx
import React, { useState } from 'react';
import { createWorkerFactory, useWorker } from '@shopify/react-web-worker';
 
const createWorker = createWorkerFactory(() => import('./worker'));
 
function HeavyComputation() {
  const [input, setInput] = useState('');
  const [result, setResult] = useState(null);
  const worker = useWorker(createWorker);
 
  const handleCompute = async () => {
    // Run heavy computation in a separate thread
    const computedResult = await worker.heavyComputation(input);
    setResult(computedResult);
  };
 
  return (
    <div>
      <input
        value={input}
        onChange={e => setInput(e.target.value)}
        placeholder="Input for heavy computation"
      />
      <button onClick={handleCompute}>Compute</button>
      {result && <div>Result: {result}</div>}
    </div>
  );
}

Best Practices

  1. Profile before optimizing: Don't optimize prematurely
  2. Use production builds: Development builds are significantly slower
  3. Implement pagination: Don't load all data at once
  4. Optimize images: Use proper formats, sizes, and lazy loading
  5. Minimize state updates: Batch updates when possible
  6. Use keys properly: Always use stable, unique keys for list items
  7. Avoid inline function definitions: They create new functions on each render
  8. Implement proper error boundaries: Prevent the entire app from crashing

By applying these optimization techniques, you can significantly improve the performance of your React applications, leading to better user experiences and higher engagement.

more React guides

  • React Hooks Complete Guide2 min read
  • Mastering React Context API3 min read