Should you optimize every re-render?
Tags: react, performance, javascript
Published: 2021-12-07 10:35:35

---
canonical_url: https://imkev.dev/optimizing-rerenders
---

Cross-posted from https://imkev.dev/optimizing-rerenders

One of the more frequent discussions I have with my colleagues is whether we should be investing the effort to optimize a React re-render (re-render).

React determines which components need to be re-rendered during the `render` phase. During this phase, React traverses the `current` Fiber tree and creates a list of [effects](https://reactjs.org/docs/hooks-overview.html#effect-hook) that need to be applied to the Fiber nodes. The `current` tree reflects the state of the application that was used to render the UI. As React processes effects, it clones the current tree and performs these updates on the `current` tree, resulting in a new `workInProgress` tree. Once all updates are processed, React will flush the `workInProgress` tree to the DOM and this will become the `current` tree. If you're interested in more detail, I cover the [React Reconciliation](/react-reconciliation-performance-measures) algorithm in a separate post.

A render is usually required whenever your props or state change.

```jsx
const MyComponent = React.memo(({ data }) => {
  return (
    <ul>
      {data.map((n) => (
        <li key={n}>{n}</li>
      ))}
    </ul>
  );
});
```

In the example above, if `data` changes, then we need to re-render the component with the latest values so these changes are reflected on the screen. Since we know that the component's output is dependent on `data`, if `data` does not change, then there is no need to recalculate the output as that is also unchanged. This allows us to use [`React.memo`](https://reactjs.org/docs/react-api.html#reactmemo) or [`React.PureComponent`](https://reactjs.org/docs/react-api.html#reactpurecomponent).

What we _do not_ want, is to re-render the component when `data` does not change. This is what I refer to as an _unnecessary re-render_.

## Not all re-renders are bad

Not all re-renders are equal. Some re-renders are more CPU intensive than others. You may debug React re-rendering using the [React Profiler](https://reactjs.org/docs/optimizing-performance.html#profiling-components-with-the-chrome-performance-tab) Chrome extension.

![Chrome DevTools React Profiler showing two panels. Left panel shows components which have been re-rendered. Right panel shows time spent on each re-render](/media/blog/rerendering.png "React Profiler") {.centered}

The left column shows which components have been re-rendered, while the right column shows you how many times the component has re-rendered. Each re-rendering also includes the component's self-time - the time it takes to execute the `render()` method for that component.

In most cases, the time spent rendering each component is a few milliseconds. This has led to the argument that [not all unnecessary re-renders are bad](https://mobile.twitter.com/iamakulov/status/1362397452884717571); a statement I have to disagree with\*.

While not all re-renders are equally important, I believe that _all_ unnecessary re-renders should be eliminated to improve your applications reliability.

```jsx
const ClickCounter = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <>
      <button onClick={handleClick}>Update counter</button>
      <Counter count={count} />
      <MyComponent data={["A", "B", "C"]} />
    </>
  );
};
```

Demo: [/src/ClickCounter.js](https://codesandbox.io/s/re-rendering-demo-cpo2g?file=/src/ClickCounter.js) {.caption}

Whenever `count` is updated, `MyComponent` will be re-rendered, even if it is not dependent on `count`. This is caused as you are passing a new array reference on each render.

```javascript
["A", "B", "C"] === ["A", "B", "C"]; // false
```

The correct solution would be to create a constant and place it outside of the `ClickCounter` component.

```jsx
const data = ["A", "B", "C"];

const ClickCounter = () => {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <>
      <button onClick={handleClick}>Update counter</button>
      <Counter count={count} />
      <MyComponent data={data} />
    </>
  );
};
```

Demo: [/src/ClickCounter.js](https://codesandbox.io/s/re-rendering-demo-solution-k6p0z?file=/src/ClickCounter.js) {.caption}

This no longer renders `MyComponent` whenever the user clicks on the `button` to update `count`. But how much faster is our application following this optimization? Most likely, the improvements are negligible. So does this mean you shouldn't bother optimizing your re-renders?

```jsx
const data = ["A", "B", "C"];

const Demo = () => {
  const location = useLocation();

  return (
    <>
      <span>{location.pathname}</span>
      <ul>
        <li>
          <MyComponent data={["A", "B", "C"]} />
        </li>
        <li>
          <MyComponent data={data} />
        </li>
      </ul>
    </>
  );
};
```

Demo: [/src/Demo.js](https://codesandbox.io/s/re-rendering-demo-bug-tbkhm?file=/src/Demo.js) { .caption }

In the example above, we have two instances of `MyComponent`; one which re-renders on each render and one which is correctly optimized. The `Demo` itself component will render whenever `location` changes, using the `useLocation` hook from [React Router](https://reactrouter.com/).

In most cases, these two components will behave identically. But in the first `MyComponent`, there is the premise for obscure bugs which would be hidden by causing the component to needlessly re-render.

```jsx
const MyComponent = memo(({ data }) => {
  const location = window.location;

  return (
    <>
      <span>{location.pathname}</span>
      <ul>
        {data.map((n) => (
          <li key={n}>{n}</li>
        ))}
      </ul>
    </>
  );
});
```

Demo: [/src/MyComponent.js](https://codesandbox.io/s/re-rendering-demo-bug-tbkhm?file=/src/MyComponent.js) { .caption }

If the rendered output of a component is dependent on more than its props, then needlessly re-rendering the component may hide this issue. In `MyComponent`, the rendered output includes the `location.pathname`, which would change whenever the URL changes. If the component does not re-render, then the updated `location.pathname` would not be visible on the screen. As a result, the `MyComponent` which needlessly re-renders would reflect this change on the next render, while the optimized component would not.

```jsx
const MyComponent = memo(({ data }) => {
  const location = useLocation();

  return (
    <>
      <span>{location.pathname}</span>
      <ul>
        {data.map((n) => (
          <li key={n}>{n}</li>
        ))}
      </ul>
    </>
  );
});
```

Of course in this example, we are simplifying things greatly and the issue is both easy to find and fix. However, from my experience, sometimes these bugs could go unnoticed for a very long time, making them very difficult to debug and eventually resolve.

### Other unnecessary re-renders

There are other forms of unnecessary renders. Similar to the new array reference, passing an object or a function will cause the component to re-render.

```jsx
return <MyComponent data={{ title: "Title" }} />;
```

This could be mitigated by either placing the object outside of the component's render method or memoizing the prop using [`React.useMemo`](https://reactjs.org/docs/hooks-reference.html#usememo). The latter is usually required if the prop is dependent on other prop or state variables.

```jsx
return (
  <MyComponent
    onClick={() => {
      doSomething(a, b);
    }}
  />
);
```

React includes [`React.useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback) which returns a memoized callback function.

```jsx
const onClickHandler = React.useCallback(() => {
  doSomething(a, b);
}, [a, b]);

return <MyComponent onClick={onClickHandler} />;
```

Passing a React component as a prop will have a similar effect.

```jsx
const Demo = () => {
  return <MyComponent header={<Header />} />;
};
```

And once again, the recommended solution is similar.

```jsx
const Header = <Header />;

const Demo = () => {
  return <MyComponent header={Header} />;
};
```

## Conclusion

While the performance improvements from optimizing every render may not always be significant, maintaining a [pure](https://en.wikipedia.org/wiki/Pure_function) codebase makes your work more predictable. If the output of every component is dependent on its inputs, then you minimize the risk of unwanted side effects. And you also get good performance "out-of-the-box".

Let me know what you think. 🤙

## Recommended Reading

- [Inside Fiber: in-depth overview of the new reconciliation algorithm in React](https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react)
- [In-depth explanation of state and props update in React](https://indepth.dev/posts/1009/in-depth-explanation-of-state-and-props-update-in-react)

\*_[Ivan Akulov](https://mobile.twitter.com/iamakulov) is an expert on web performance and I recommend reading [his work](https://3perf.com/). This is by no means an attempt at bashing a colleague's work._


---

Photo by [Tomáš Malík](https://unsplash.com/@malcoo?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/t/nature?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)