April 9, 2023

0

views

Re-render: The React Non-Issue

React is all about rendering and re-rendering


Writings

Crafted, long form thoughts and ideas.

Performance is always a concern among developers, often before it's even necessary. However, using React, there are still several misunderstandings and one of them, and perhaps the main one is rerenders.

100 renders can be faster than 1 slow render.

But how can re-rendering not be an issue?

The Non-Issue: Re-render

First, to understand why it's not an issue, we must understand how React rendering happens.

As it is said in the official documentation, we can separate it into three steps:

Normally, when we think about re-renders, we think about updates to the DOM, but this only happens in the Commit phase, this phase only happens after the Render phase. Therefore, your component can be rendered, and not generate a change in the DOM (Commit), as it will compare the previous rendering to ensure that it will perform the minute of possible changes, this step is known as Reconciliation.

In the example, even if the Foo component is re-rendered every time the counter is changed, it won't result in an update to the DOM, so even with new re-renders won't have a performance penalty, so not an issue.

function Foo() {
  console.log("render: FOO");
  return <div>FOO!</div>;
}
 
function Counter() {
  const [count, setCount] = React.useState(0);
  console.log("render: Counter");
  return (
    <>
      <Foo />
      <button onClick={() => setCount((c) => c + 1)}>{count}</button>
    </>
  );
}

So when can re-rendering be an issue?

The Actual Issue: Slow rendering

While React is generally known for its efficiency in rendering elements, there are situations where rendering performance can be compromised, necessitating attention and optimization. This usually happens in the Rendering phase, particularly when there are expensive computation, rendering a large number of elements simultaneously, or loading resource-heavy dependencies, resulting in slow rendering.

To solve this problem there are several ways, but first it is important to measure.

You only optimize what you measure

Measure

For that, we can use @ChromeDevTools Performance tab. In addition, we can also use React DevTools to view the rendering time in detail and ensure that improvements are being made and not regressions.

Performance Techniques

Now that we can measure improvements or make regressions, we can use Performance Hooks:

The first two hooks presented, useMemo and useCallback are commonly used to optimize re-rendering performance, allowing to skip unnecessary work. These hooks, using Memoization techniques, make it possible to cache computationally costly calculations in memories or even not render again if the data has not changed, using a stable reference.

However, sometimes it is not possible to skip re-rendering, as visual feedback of the change is required. But luckily, with the release of React 18, the new useTransition and useDeferredValue hooks allow you to separate what needs to be updated synchronously, such as a text input, from what can be updated asynchronously, i.e., what has less priority , as a list of users when filtered. This separation promotes a sense of performance gain, as it allows what needs to be responsive to remain responsive.

We also have the memo function, which helps you skip re-rendering a component when its props are unchanged.

Caveat

By design, React was made to generate multiple renders and it was special in the beginning because it managed to do this with good performance and a simple mental model.

That's why it's important not to make premature optimizations, and measure any change made in order to optimize something, to ensure that it's not actually a a regression.

Kent C. Dodds has an excellent blogpost on the topic, where he goes into more depth about the cost of avoiding re-rending and how it can slow down your app.

Conclusion