February 09, 2020
When developing in React, you will likely run into scenarios where components are rerendering more than you would expect; which can have a direct impact on an application’s perceived performance.
And with the introduction of React Hooks, developers now have even more ways to inadvertently trigger rerenders (looking at you useEffect
!).
Thankfully React gives developers several tools to help them find the source of unnecessary rerenders. In this post I’ll discuss three of them: DevTools Profiler, React.memo, and React.Profiler.
DevTools Profiler is a fantastic browser plugin that is currently available in Chrome & Firefox (there is also a Node version). Check out the docs to learn more about specific features.
Version 4 of React DevTools — released August 15, 2019 — came with a great new feature called “Why did this render?”.
To use this tool, simply install Profiler and turn on the “Record why each component rendered while profiling.” option. You can then run Profiler while interacting with your app, focusing on whichever components may be rendering unnecessarily.
After you end the Profiler session, you’ll be able to drill down into individual components to see their render metrics. Under the “Why did this render?” heading you’ll see a list of reasons the component rendered/rerendered.
Common reasons for rerendering:
useState
’s setState
methode being called)Of all the debugging tools, I’d say this is the easiest and fastest to set up and use. But there is one shortcoming: there is no way to inspect the value of the props that changed; and it’s often helpful to be able to inspect prop values to get a better understanding of what is changing, and why.
To get this data you can use another tool: React.memo
.
React v16.6.0 gave us a new React.memo method that can be used with both functional and class-based components to give us more control over rerenders, similar to the shouldComponentUpdate
class component method. Not only is it a good tool for controlling rerenders, it can also be a helpful tool when trying to find the cause of rerenders.
The key to debugging rerenders is to use the second optional argument of React.memo
which is an “isEqual” function that takes two arguments, prevProps
and nextProps
, and gives you control over whether a component should change. See the React docs for memo
for more details.
Now with access to prevProps
and nextProps
, you can easily view what is changing and determine the root cause of rerenders:
const memoizedComponent = React.memo(MyComponent,
(prevProps, nextProps) => {
console.log(prevProps.thing === nextProps.thing);
/*
When using this function you always need to return
a Boolean. For now we'll say the props are NOT equal
which means the component should rerender.
*/
return false;
}
)
Side note: while you can use React.memo
to manually prevent rerenders once you find the issue, I highly recommend dealing with the root cause — which is more often than not a prop that is being unnecessarily recreated on every render. Otherwise you’ll end up band-aiding every component with React.memo
which will result in lots equality checks, plus data being stored in memory.
Finally, let’s take a look at the React.Profiler API, which gives developers additional data points that can be used to debug performance issues.
With React.Profiler
, developers can wrap their JSX elements with a <Profiler>
component, which takes two props:
return (
<Profiler
id="test1"
onRender={(...args) => {
{ [1]: phase, [2]: actualDuraction } = args;
console.log({ phase, actualDuration })
}}
>
<App />
</Profiler>
);
Here a few things you can check when debugging rerenders using React.Profiler
:
mount
phase after the initial render; it should always be updated
.actualDuraction
should go down after the initial render. If it stays the same or goes up, you are likely not rendering children efficiently.startTime
.baseDuration
will tell you the worst case scenario when a component rerenders. Components with the highest baseDuration
are the ones you want to pay extra attention to when optimizing rerenders.That’s it! Happy debugging!
A blog by Bryce Dooley — a Software Engineer, Dad, Husband, and Productivity Nerd — based out of Boston, MA.