Alex Sidorenko

How to Detect Slow Renders in React?

October 06, 2021

Improving React app performance often comes down to finding bottlenecks and fixing them. One well-placed memoization can make a slow app fast again. But how do we detect the bottlenecks?

Profile the problem

Open React Developer Tools Profiler tab. Click the record button to start profiling. Interact with the part of your app that feels slow, then click the record button again to stop profiling.

Analyze the results

Find a slow commit you want to improve. You can see the commits bar in the right top corner of profiling results. For more info on commits, check out React Docs - Browsing Commits.

In our case, 1st, 2nd, and 4th commits are slow. They take more than 300ms to render. Every response to a user action that takes more than 100ms breaks the connection between the action and the result (RAIL: A User-Centric Model For Performance).

Now let’s pick one of these commits and check the “Flamegraph” to see what is causing this poor performance.

The Flamegraph shows our components tree. We can see that component Home and its entire sub-tree re-renders. The SearchResults component responsible for the main UI change is pretty fast and takes only 7.4ms to render. The SlowComponent takes most of the rendering time. It is the bottleneck.

Fix the bottleneck

Let’s look into the code of a SlowComponent:

const SlowComponent = () => {
  // Expensive calculation that takes 300+ms
  const n = [...Array(3000000).keys()].reduce((p, c) => p + c)

  return <p>Expensive calculation - {n}</p>

We can wrap our expensive calculation with useMemo to make sure it only runs when necessary. And since we don’t rely on any of the props, we can leave the dependency array empty. This way, our expensive calculation will not be re-triggered every time SlowComponent re-renders.

const SlowComponent = () => {
  const n = useMemo(() => {
    // Expensive calculation that takes 300+ms
    return [...Array(3000000).keys()].reduce((p, c) => p + c)
  }, [])

  return <p>Expensive calculation - {n}</p>

Now let’s analyze performance again.

The UI feels faster already. Let’s check the commits.

The 1st, 2nd, and 4th commits are still the slowest. But they each take around 12-17ms to render, which is 14 times faster than before. Let’s analyze the Flamegraph to see what happened.

The SearchResults component takes the most time to render now. But since it’s only 12ms, we have nothing to worry about. And now that we put our memoization in place, the SlowComponent takes only 0.3ms to render.