React Performance Tuning - How to optimize your App

3 min read

The Astro logo with the word One.

React is fast by default but can feel sluggish if you don’t pay attention to performance. Luckily there are proven techniques to optimize React apps.

In this article we’ll examine methods for tuning React performance through:

  • Avoiding unnecessary re-renders
  • Virtualizing long lists
  • Code splitting and chunking
  • Preventing UI thrashing
  • Properly using memoization

Avoid Unnecessary Re-renders

A common culprit of slow rendering is unnecessary re-renders of components. Instead we should selectively render.

By default, React re-renders when state or props change. With React.memo we can control this:

// Only rerenders if props change
const MyComponent = React.memo(function MyComponent(props) {
  /* only render if props change */
});

Wrap functional components that render often but don’t change much with React.memo.

For classes we use React.PureComponent:

class MyComponent extends React.PureComponent {
  /* only rerenders on state/prop changes */ 
}

This avoids wasted re-rendering cycles.

Effect Hook Dependencies

By default, effects run on every render. We can restrict this with the second array argument:

useEffect(() => {
  // Runs only on count or term change
}, [count, term])

Define the minimal necessary dependencies.

Virtualize Long Lists

Rendering long lists becomes highly inefficient. Virtualization means only rendering visible elements.

Libraries like react-window 🔗 and react-virtualized 🔗 help here.

Say we have a long list of images:

function MyList({photos}) {
  return (
    <div>
      {photos.map(photo => (
        <img src={photo.url} />  
      ))}
    </div>
  )
}

With react-window we can render more efficiently:

import {FixedSizeList} from 'react-window';

function MyList({photos}) {
  return (
    <FixedSizeList  
      height={800}
      width={800}
      itemSize={200}
      itemCount={photos.length} 
    >
      {({index, style}) => (
        <img {...style} src={photos[index].url} />
      )}
    </FixedSizeList>
  )
}

Only visible items are rendered. Virtual lists revolutionize UIs with tabular data.

Code Splitting

Instead of loading one huge app bundle, React can split this into “chunks”. Users then only load necessary code.

The React docs demo code splitting 🔗 via React.lazy:

// Lazily load component
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <React.Suspense fallback={<Spinner />}>
      <OtherComponent />
    </React.Suspense>
  ); 
}

OtherComponent is now loaded asynchronously.

This also works with server side rendering via Loadable Components 🔗.

Chunking reduces load times by splitting bundles.

Avoid UI Thrashing

“UI Thrashing” happens when too many state changes cause the DOM to re-render frequently.

Instead, we can batch state changes:

function handleSubmit(e) {
  e.preventDefault();
  
  // Batch multiple state changes
  setIsSubmitting(true);
  setErrors({});
  submitForm();
  setIsSubmitting(false); 
}

This will only re-render once instead of multiple times.

Also avoid repeatedly setting the same values.

Use Memoization

Memoization 🔗 caches computed values between renders.

Useful for expensive computations:

// Result cached between re-renders  
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

But don’t overuse everywhere. Benchmarking remains important.

Memoization reduces duplicate calculations.

Recap

  • Selective re-rendering via React.memo and React.PureComponent
  • Virtualize long lists for efficient rendering
  • Code splitting for faster load times
  • Batch state changes to prevent “thrashing”
  • Memoize expensive computations

With these techniques, even large React apps can be optimized.

Take Your Skills to the Next Level

If you enjoyed this article, you may also like these related pieces:

Engineering Trust ISO 27001 TISAX SOC 2 Risk Management Compliance as Code Engineering Trust ISO 27001 TISAX SOC 2 Risk Management Compliance as Code

Stop guessing.
Start knowing.

Security is a solvable engineering problem. Let's fix it.

J
© 2026 Julian Koehn. Engineered in Germany.