Skip
Arish's avatar

26. React Performance Basics


React Performance Optimization

Understanding and optimizing React performance is crucial for building fast applications.

How React Renders

State/Props Change → Component Re-renders → Virtual DOM Diff → DOM Updates

React re-renders a component when:

  1. Its state changes
  2. Its props change
  3. Its parent re-renders
  4. Context it consumes changes

React.memo

Prevent re-renders when props haven't changed:

jsx
1// Without memo - re-renders on every parent render
2function ExpensiveList({ items }) {
3  console.log('Rendering list...')
4  return items.map(item => <Item key={item.id} item={item} />)
5}
6
7// With memo - only re-renders when items change
8const ExpensiveList = React.memo(function ExpensiveList({ items }) {
9  console.log('Rendering list...')
10  return items.map(item => <Item key={item.id} item={item} />)
11})

Custom Comparison

jsx
1const MemoizedComponent = React.memo(
2  function Component({ user, onClick }) {
3    return <div onClick={onClick}>{user.name}</div>
4  },
5  (prevProps, nextProps) => {
6    // Return true if props are equal (skip re-render)
7    return prevProps.user.id === nextProps.user.id
8  }
9)

useMemo for Expensive Calculations

jsx
1function ProductList({ products, filter }) {
2  // ❌ Runs on every render
3  const filteredProducts = products.filter(p => p.category === filter)
4  
5  // ✅ Only recalculates when dependencies change
6  const filteredProducts = useMemo(() => {
7    console.log('Filtering products...')
8    return products.filter(p => p.category === filter)
9  }, [products, filter])
10  
11  return (
12    <ul>
13      {filteredProducts.map(p => <ProductItem key={p.id} product={p} />)}
14    </ul>
15  )
16}

useCallback for Stable Function References

jsx
1function Parent() {
2  const [count, setCount] = useState(0)
3  
4  // ❌ New function on every render
5  const handleClick = () => console.log('clicked')
6  
7  // ✅ Stable reference
8  const handleClick = useCallback(() => {
9    console.log('clicked')
10  }, [])
11  
12  return <MemoizedChild onClick={handleClick} />
13}
14
15// Child only re-renders if onClick reference changes
16const MemoizedChild = React.memo(function Child({ onClick }) {
17  console.log('Child rendered')
18  return <button onClick={onClick}>Click</button>
19})

Avoiding Unnecessary Re-renders

Move State Down

jsx
1// ❌ Bad - entire app re-renders on input change
2function App() {
3  const [text, setText] = useState('')
4  
5  return (
6    <div>
7      <Header />
8      <input value={text} onChange={e => setText(e.target.value)} />
9      <ExpensiveComponent />
10      <Footer />
11    </div>
12  )
13}
14
15// ✅ Good - only SearchBox re-renders
16function App() {
17  return (
18    <div>
19      <Header />
20      <SearchBox />
21      <ExpensiveComponent />
22      <Footer />
23    </div>
24  )
25}
26
27function SearchBox() {
28  const [text, setText] = useState('')
29  return <input value={text} onChange={e => setText(e.target.value)} />
30}

Lift Content Up

jsx
1// ❌ Bad - children re-render on color change
2function ColorPicker() {
3  const [color, setColor] = useState('red')
4  
5  return (
6    <div style={{ backgroundColor: color }}>
7      <input value={color} onChange={e => setColor(e.target.value)} />
8      <ExpensiveTree /> {/* Re-renders every time! */}
9    </div>
10  )
11}
12
13// ✅ Good - children passed as prop don't re-render
14function App() {
15  return (
16    <ColorPicker>
17      <ExpensiveTree />
18    </ColorPicker>
19  )
20}
21
22function ColorPicker({ children }) {
23  const [color, setColor] = useState('red')
24  
25  return (
26    <div style={{ backgroundColor: color }}>
27      <input value={color} onChange={e => setColor(e.target.value)} />
28      {children} {/* Doesn't re-render! */}
29    </div>
30  )
31}

Virtualization for Long Lists

Only render visible items:

bash
1npm install @tanstack/react-virtual
jsx
1import { useVirtualizer } from '@tanstack/react-virtual'
2
3function VirtualList({ items }) {
4  const parentRef = useRef(null)
5  
6  const virtualizer = useVirtualizer({
7    count: items.length,
8    getScrollElement: () => parentRef.current,
9    estimateSize: () => 50,
10  })
11  
12  return (
13    <div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
14      <div
15        style={{
16          height: `${virtualizer.getTotalSize()}px`,
17          position: 'relative',
18        }}
19      >
20        {virtualizer.getVirtualItems().map(virtualItem => (
21          <div
22            key={virtualItem.key}
23            style={{
24              position: 'absolute',
25              top: 0,
26              left: 0,
27              width: '100%',
28              height: `${virtualItem.size}px`,
29              transform: `translateY(${virtualItem.start}px)`,
30            }}
31          >
32            {items[virtualItem.index].name}
33          </div>
34        ))}
35      </div>
36    </div>
37  )
38}

Debouncing User Input

jsx
1function SearchInput() {
2  const [query, setQuery] = useState('')
3  const [debouncedQuery, setDebouncedQuery] = useState('')
4  
5  useEffect(() => {
6    const timer = setTimeout(() => {
7      setDebouncedQuery(query)
8    }, 300)
9    
10    return () => clearTimeout(timer)
11  }, [query])
12  
13  useEffect(() => {
14    if (debouncedQuery) {
15      performSearch(debouncedQuery)
16    }
17  }, [debouncedQuery])
18  
19  return (
20    <input
21      value={query}
22      onChange={e => setQuery(e.target.value)}
23      placeholder="Search..."
24    />
25  )
26}

Profiling

Use React DevTools Profiler:

  1. Open React DevTools
  2. Go to "Profiler" tab
  3. Click "Record"
  4. Interact with your app
  5. Click "Stop"
  6. Analyze render times and causes

Key Performance Tips

  1. Don't optimize prematurely - Measure first
  2. Use React.memo strategically - Not everywhere
  3. Keep state local - Only lift when needed
  4. Virtualize long lists - 1000+ items
  5. Lazy load components - Code splitting
  6. Use Web Workers - Heavy computations

Performance optimization is about making informed decisions!