# React Performance Optimization Techniques

**Author:** kelexine  
**Date:** 2024-11-28  
**Category:** React  
**Tags:** React, Performance, Optimization, Hooks, Memoization  
**URL:** https://kelexine.is-a.dev/blog/react-performance-optimization

---

# React Performance Optimization Techniques

Performance optimization is crucial for creating smooth, responsive React applications. In this comprehensive guide, we'll explore various techniques from React 19's automatic optimizations to traditional patterns.

## The React 19 Paradigm Shift

With React 19, the performance landscape has changed correctly. The introduction of the **React Compiler** and **Server Components** means many manual optimizations (like `useMemo` and `useCallback`) are becoming automated or unnecessary.

### 1. React Compiler (Automatic Memoization)

The biggest game-changer is the **React Compiler**. It automatically memoizes your components, props, and dependency arrays.

**Before (Manual):**
```javascript
const MemoizedComponent = React.memo(({ data }) => ...);
const handleClick = useCallback(() => ..., []);
```

**After (React 19):**
Just write idiomatic code. The compiler handles the rest, ensuring efficient re-renders without the boilerplate.

## Understanding React Performance (Legacy & Core)

Before diving into optimization techniques, let's understand what affects React performance:

- **Re-renders**: Unnecessary component re-renders
- **Bundle size**: Large JavaScript bundles
- **Memory leaks**: Improper cleanup of effects
- **Expensive computations**: Heavy calculations in render methods

## React DevTools Profiler

First, let's set up performance monitoring:

```javascript
// Install React DevTools Profiler
// Chrome Extension: React Developer Tools

// Enable profiling in development
if (process.env.NODE_ENV === 'development') {
  const { Profiler } = require('react');
  
  // Wrap your app with Profiler
  <Profiler id="App" onRender={onRenderCallback}>
    <App />
  </Profiler>
}

function onRenderCallback(id, phase, actualDuration, baseDuration, startTime, commitTime) {
  console.log('Component:', id);
  console.log('Phase:', phase);
  console.log('Actual Duration:', actualDuration);
  console.log('Base Duration:', baseDuration);
}
```

## 2. Server Components (RSC) for Zero-Bundle-Size

React Server Components allow you to render components on the server, sending zero JavaScript to the client. This is massive for performance.

```javascript
// This runs ONLY on the server
import db from './database';

async function ProjectList() {
  const projects = await db.query('SELECT * FROM projects');
  
  return (
    <ul>
      {projects.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}
```

**Why it helps:**
- **No API latency waterfall** on the client.
- **Zero bundle size** for backend dependencies (like the db library).
- **Faster FCP** (First Contentful Paint) as HTML is streamed.

## Memoization Techniques (Legacy/Manual)

> **Note:** With React Compiler, these are less necessary but still useful for specific cases.

### React.memo for Components

```javascript
// Without memoization
function ExpensiveComponent({ data }) {
  console.log('Rendering ExpensiveComponent');
  return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>;
}

// With memoization
const ExpensiveComponent = React.memo(({ data }) => {
  console.log('Rendering ExpensiveComponent');
  return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>;
});

// Custom comparison function
const ExpensiveComponent = React.memo(({ data }) => {
  return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>;
}, (prevProps, nextProps) => {
  return prevProps.data.length === nextProps.data.length &&
         prevProps.data.every((item, index) => item.id === nextProps.data[index].id);
});
```

### useMemo for Expensive Computations

```javascript
import { useMemo } from 'react';

function ExpensiveCalculation({ numbers }) {
  // This calculation runs on every render
  // const sum = numbers.reduce((acc, num) => acc + num, 0);
  
  // With useMemo - only recalculates when numbers change
  const sum = useMemo(() => {
    console.log('Calculating sum...');
    return numbers.reduce((acc, num) => acc + num, 0);
  }, [numbers]);
  
  return <div>Sum: {sum}</div>;
}
```

### useCallback for Functions

```javascript
import { useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  // Without useCallback - creates new function on every render
  // const handleClick = () => setCount(count + 1);
  
  // With useCallback - memoizes the function
  const handleClick = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);
  
  return (
    <div>
      <ExpensiveChild onClick={handleClick} />
      <input value={name} onChange={(e) => setName(e.target.value)} />
    </div>
  );
}
```

## Virtualization for Large Lists

```javascript
import { FixedSizeList } from 'react-window';

function LargeList({ items }) {
  const Row = ({ index, style }) => (
    <div style={style} className="p-4 border-b border-border-accent">
      {items[index].name}
    </div>
  );
  
  return (
    <FixedSizeList
      height={400}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}
```

## Code Splitting and Lazy Loading

```javascript
import { lazy, Suspense } from 'react';

// Regular import
// import HeavyComponent from './HeavyComponent';

// Lazy import
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}
```

## Optimizing Re-renders

### useRef for Mutable Values

```javascript
import { useRef, useEffect } from 'react';

function Timer() {
  const intervalRef = useRef();
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    
    return () => clearInterval(intervalRef.current);
  }, []);
  
  return <div>Count: {count}</div>;
}
```

### Preventing Unnecessary Re-renders

```javascript
// Bad: Creates new object on every render
function BadComponent() {
  const user = { name: 'John', age: 30 };
  return <UserProfile user={user} />;
}

// Good: Memoized object
function GoodComponent() {
  const user = useMemo(() => ({ name: 'John', age: 30 }), []);
  return <UserProfile user={user} />;
}
```

## Bundle Optimization

### Tree Shaking

```javascript
// Bad: Imports entire library
import _ from 'lodash';
const result = _.debounce(fn, 300);

// Good: Imports only what you need
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
```

### Dynamic Imports

```javascript
// Load heavy library only when needed
async function loadHeavyLibrary() {
  const { processData } = await import('./heavyLibrary');
  return processData();
}
```

## Image Optimization

```javascript
import { LazyLoadImage } from 'react-lazy-load-image-component';

function OptimizedImage({ src, alt }) {
  return (
    <LazyLoadImage
      src={src}
      alt={alt}
      effect="blur"
      placeholderSrc="/placeholder.jpg"
      className="w-full h-auto"
    />
  );
}
```

## Performance Monitoring

```javascript
// Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

function sendToAnalytics(metric) {
  // Send to your analytics service
  console.log(metric);
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
```

## Common Performance Pitfalls

### 1. Inline Functions in JSX

```javascript
// Bad
<button onClick={() => handleClick(id)}>Click</button>

// Good
const handleClick = useCallback((id) => {
  // handle click
}, []);

<button onClick={() => handleClick(id)}>Click</button>
```

### 2. Index as Key

```javascript
// Bad
{items.map((item, index) => (
  <div key={index}>{item.name}</div>
))}

// Good
{items.map((item) => (
  <div key={item.id}>{item.name}</div>
))}
```

### 3. Unnecessary State Updates

```javascript
// Bad
const [user, setUser] = useState({ name: '', age: 0 });
setUser({ ...user, name: 'John' }); // Triggers re-render

// Good
const [name, setName] = useState('');
const [age, setAge] = useState(0);
setName('John'); // Only name-related components re-render
```

## Performance Checklist

- [ ] Use React.memo for expensive components
- [ ] Use useMemo for expensive calculations
- [ ] Use useCallback for stable function references
- [ ] Implement virtualization for large lists
- [ ] Use code splitting for heavy components
- [ ] Optimize images and implement lazy loading
- [ ] Monitor Core Web Vitals
- [ ] Avoid inline functions and objects in JSX
- [ ] Use proper keys for list items
- [ ] Split state to minimize re-renders

## Tools for Performance Analysis

1. **React DevTools Profiler**: Component render analysis
2. **Chrome DevTools**: Performance tab for detailed metrics
3. **Lighthouse**: Automated performance audits
4. **Bundle Analyzer**: Bundle size optimization
5. **Web Vitals**: Real user metrics

## Conclusion

React performance optimization is an ongoing process. Start with profiling to identify bottlenecks, then apply the appropriate optimization techniques. Remember:

- **Measure first**: Use profiling tools to identify real bottlenecks
- **Optimize wisely**: Don't over-optimize prematurely
- **Test thoroughly**: Ensure optimizations don't break functionality
- **Monitor continuously**: Performance can degrade over time

> **Remember**: The best optimization is often a simpler, more maintainable solution.

---

*Next up: Advanced React patterns and custom hooks...*

---

*This content is available at [kelexine.is-a.dev/blog/react-performance-optimization](https://kelexine.is-a.dev/blog/react-performance-optimization)*
