Joshua's Docs - React Performance and Optimization Notes
Light

Optimizing React Renders and Avoiding Unnecessary Re-Renders

One of the most annoying parts of React (IMHO), and often (oddly) left out of performance discussions around React...

What Causes a React Component To Re-Render

First, we should clarify what causes a React component to re-render, and the answer is pretty much... anything. Well, not exactly, but close:

  • An update to the state of that component. This includes changes to:
    • props (regardless of actual usage)
    • values derived from props
    • The actual explicit state values (e.g. via setState)
  • And / or, ANY of the component's parents re-rendering
    • This is a very common annoyance - if your component re-renders, it blindly re-renders all of its children as well!

Unfortunately, the official React docs don't do the best job of specifying this behavior, and the two best resources I have found for explaining what causes re-renders are from external sources:

💡 / ⚠ - An easy-to-miss missed thing about prop comparison: Passing an anonymous function (aka inline function / callback) as a prop will break prop-comparison in things like React.memo and PureComponent; if the parent changes, even if the anonymous function stays the same, React will always infer that the props have changed, since it has no way to compare the old anonymous function to the new one. This has been written about in many places, including here, here, and here. The useCallback hook is a good way to address this, but comes with its own caveats (ref A, ref B)

⚠ There is a lot of misleading information out there about the Virtual DOM (aka VDOM) and its benefits. People tend to incorrectly believe that React's dom-diffing means that re-renders are avoided if nothing has changed, but this is only partially true; yes, React will reduce the number of real DOM updates if nothing has changed, but React treats VDOM re-renders as inexpensive, and is part of why it lets almost anything trigger a VDOM re-render, which eventually has to be diffed against the real DOM as well. For the most part, this can stay fast because of how VDOM works, but this can also be a big problem if you have a lot of (processor heavy) logic tied to the rendering of your components.

This post from Rich Harris, "Virtual DOM is pure overhead", is a great related read.

Tracing a Re-Render

Even knowing all the above, about what causes a React re-render, it might still be hard to pin down what exactly is causing a specific component (or component tree) to re-render. Thankfully, there are tools you can use to help track down what is triggering a re-render:

This post does a great job of covering some of the best approaches for debugging excessive rerenders in React - brycedooley.com/debug-react-rerenders.

Avoiding Re-Renders

The first step to avoiding a re-render might be to simply understand why it is happening. See the above section(s) for the general reasons why a React component gets re-rendered, and how to trace the specific changes triggering yours. Regardless of the reason, there are a few general approaches for reducing unnecessary components and optimizing performance.

  • For Class-Based Components
    • Hook into the shouldComponentUpdate lifecycle method
      • You basically override it based on your set of criteria for why your component should or should not update on changed props
      • Here are some examples from the React docs on using this to optimize performance
      • Make sure to heed the warnings listed in the docs
    • Inherit your component from React.PureComponent class (by extending the class / subclassing it)
      • This basically has a predefined shouldComponentUpdate that shallow compares old props and state to new props and state, and prevents an update if they are the same
      • The examples on the React performance page also discusses the difference between using PureComponent versus hand-coding shouldComponentUpdate.
  • For Function-Based Components (e.g. a React Hooks approach)
    • Memoization is the main tool you have available when it comes to preventing re-renders with hooks
    • Here is a guide on optimizing with hooks.

📘 This post is a good alternative summary of the above options.

There is some truth to the "avoid premature-optimization" argument. In general, React re-rendering in the VDOM is very fast, and you don't need to optimize it. Adding methods that check props and state and do deep comparisons to reduce re-renders actually adds overhead. For example, it would be a bad situation if over a span of time, you reduced renders from 300 to 290, but because you did so with a deep prop equality check, you added 300 NEW comparison calls. This infographic on when to use `React.memo() is a good breakdown of reasons to go down this route.

Markdown Source Last Updated:
Sun Oct 02 2022 03:45:35 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Tue Oct 20 2020 12:21:16 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby
Feedback