Skip to main content
ReactPerformanceOptimization

10 Advanced Techniques for Optimizing React Performance

December 15, 2023
11 min read
Share:

React is powerful, but without careful optimization, applications can become sluggish as they grow. Here are 10 advanced techniques I've used to keep React apps fast and responsive.


1. Memoize Expensive Computations with useMemo

When you have computations that are expensive and don't need to re-run on every render, useMemo is your best friend.

const sortedItems = useMemo(() => {
  return items.sort((a, b) => a.price - b.price);
}, [items]);

Only recalculates when items actually changes — not on every parent re-render.


2. Prevent Unnecessary Re-renders with React.memo

Wrap components that receive the same props frequently. React.memo does a shallow comparison and skips re-rendering if props haven't changed.

const ProductCard = React.memo(({ product, onAdd }) => {
  return (
    <div>
      <h3>{product.name}</h3>
      <button onClick={() => onAdd(product.id)}>Add</button>
    </div>
  );
});

Important: This only works if you also stabilize callback references with useCallback.


3. Stabilize Callbacks with useCallback

Without useCallback, every render creates a new function reference — breaking React.memo on child components.

const handleAdd = useCallback((id) => {
  setCart(prev => [...prev, id]);
}, []);

4. Virtualize Long Lists

Rendering 10,000 DOM nodes kills performance. Libraries like react-window or @tanstack/virtual only render what's visible in the viewport.

import { FixedSizeList } from 'react-window';

<FixedSizeList
  height={600}
  itemCount={items.length}
  itemSize={50}
>
  {({ index, style }) => (
    <div style={style}>{items[index].name}</div>
  )}
</FixedSizeList>

This takes you from rendering thousands of elements to rendering ~20 at any given time.


5. Code Split with React.lazy and Suspense

Don't load everything upfront. Split your bundle so users only download what they need.

const Dashboard = React.lazy(() => import('./Dashboard'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Dashboard />
    </Suspense>
  );
}

Combined with route-based splitting, this can dramatically reduce initial load time.


6. Debounce Expensive Operations

Search inputs, window resize handlers, scroll listeners — these fire rapidly. Debounce them to avoid hammering your app with state updates.

const debouncedSearch = useMemo(
  () => debounce((query) => setSearchTerm(query), 300),
  []
);

7. Use Keys Correctly in Lists

React uses keys to track which items changed. Using array indices as keys causes unnecessary re-renders when items are reordered or removed.

// Bad - index keys cause issues on reorder
{items.map((item, i) => <Item key={i} {...item} />)}

// Good - stable unique keys
{items.map(item => <Item key={item.id} {...item} />)}

8. Avoid Inline Object/Array Creation in JSX

Every render creates a new reference, which means child components always see "new" props.

// Bad - new object every render
<Map center={{ lat: 0, lng: 0 }} />

// Good - stable reference
const center = useMemo(() => ({ lat: 0, lng: 0 }), []);
<Map center={center} />

9. Profile Before Optimizing

Don't guess where the bottleneck is. Use React DevTools Profiler to measure actual render times and identify which components re-render unnecessarily.

Steps:

  1. Open React DevTools → Profiler tab
  2. Click Record
  3. Interact with your app
  4. Stop recording and analyze the flamegraph

Look for components that render frequently with the same props — those are candidates for React.memo.


10. Use Transitions for Non-Urgent Updates

React 18's useTransition lets you mark state updates as non-urgent, keeping the UI responsive during heavy computations.

const [isPending, startTransition] = useTransition();

function handleFilter(value) {
  // Urgent: update input immediately
  setInputValue(value);

  // Non-urgent: filter results can wait
  startTransition(() => {
    setFilteredResults(filterItems(value));
  });
}

Summary

TechniqueBest For
useMemoExpensive calculations
React.memoComponents with stable props
useCallbackStabilizing function references
VirtualizationLong lists (100+ items)
Code splittingReducing initial bundle size
DebouncingRapid-fire events (search, scroll)
Proper keysDynamic lists
Stable referencesPreventing unnecessary re-renders
ProfilingFinding actual bottlenecks
useTransitionHeavy non-urgent updates

The most important lesson: measure first, optimize second. React is already fast for most use cases. Only add complexity when profiling shows a real problem.


Have questions about React performance? Connect with me on Twitter or LinkedIn!

Comments

Comments section coming soon! For now, feel free to reach out on Twitter or LinkedIn.