How I eliminate ALL unnecessary Rerenders in React

Vitaliysteffensen
8 min readApr 16, 2022

--

We all love React for its performance and its simple state manipulation, but it is not all a bed of roses. When you start to build more and more complex applications you will oftentimes find yourself falling into the trap of unnecessary rerendering… And in many cases, you might not even know it!

First, we will dig into what is causing these unnecessary renders and how we can spot these different causes easily.

Afterward, we will dig further into different techniques to prevent these rerenders and when these techniques should apply. I strongly encourage you to get familiar with most of them, since there is no single tool, that fits all scenarios.

Table of contents

  1. Ensure I don’t encode any infinite loops
  2. Use Dynamic Programming
  3. Use the useReducer hook
  4. Prevent unnecessary unmounting
  5. Optimized useEffect dependencies

Understanding my rerenders

As we all know, React recomputes and rerenders all components, every time their states change. That also applies if the component is dependent on an external state such as props, context and hooks.

But this is not all. Why? Well because by default React will also rerender a component every time its parent gets rerendered.

Rerendering is recomputing. This means that the browser won’t only rerender your HTML elements, but it will also run all the functions that are called before the return statement. This means that these rerenders quickly can build up a vast performance decrease.

As you can hear, there are a lot of rerender-triggers. But how exactly do we spot them?

If you are like me, you might be using “console.log” for this. But I recently realized that there is a much more efficient method.

I found out that the React Developer Tools includes a profiler, which has a built-in recorder that visualizes and informs you about all renders in your app. You can even enable a reasoning recorder, as shown below:

This tool provides insights into all the components that are affected by a specific action. By clicking a component in the provided tree, you can even gather insights into the component's props and states!

Preventing the rerenders

1. Ensure I don’t encode any infinite loops

The most crucial outcome of unnecessary re-renders is when you include infinite loops in your code. This can lead to horrible performance and ultimately runtime errors.

The most common infinite loops to look out for are:

  • Updating dependencies in useEffect
useffect({
setSomeState()
}, [someState])

The example above shows a useEffect setting the state of its own dependency, which will result in infinite recursion.

  • Calling a function instead of referencing it
<button onClick={updateStateFunction()}>Click me</button>

The code above shows a function being called in an onClick attribute. This function gets called as soon as we initialize the JSX. Meaning when we try to render the page the function gets called and therefore causes another render. This recursion will go on until React throws a runtime error.

To avoid this, we should do as in the examples below:

<button onClick={() => updateStateFunction()}>Click me</button>

-

<button onClick={updateStateFunction}>Click me</button>
  • Deriving data in the Render Method

If you were to call a setState in the Render function on a class component, you will cause an infinite loop. The reason is that when we are setting a state in the render function it will cause another render, which runs the render function again and again triggering another render… This will again go on until we get an error.

A core principle of React is to have the Render function as a pure function.

2. Use dynamic programming

We all know that React re-renders a component every time its props or state changes. But this means that even if the props or states get set to the same value as they currently are, the entire component will still rerender.

Not only that, but even if the parent component re-renders the entire child component will re-render again…

Both of these scenarios involve unnecessary rerendering. But React includes two methods to solve this:

If you are creating a class-based component you can extend it with PureComponent. This will implement a shallow prop and state comparison on the component and all the unnecessary re-rerender scenarios mentioned above.

React.memo is the equivalent of a pure component. React.memo is a HOC(Higher Order Component) you can wrap around your functional component to solve the same problem as the pure component.

There is one downside to the memoized component. It is that it only compares props and not states or contexts.

When using React.PureComponent or React.memo you are trading memory for performance. Therefore you should use it wisely. A rule of thumb is that you should only apply it when:

  • It is a pure component
  • It rerenders given the same props or state.
  • It rerenders often

But if your component introduces side effects, then it component should not be pure or memoized. Some scenarios of side effects are:

  • Modifying any external variable or object property
  • Logging data to the console
  • Writing Data to a file
  • Writing data to the network
  • Triggering any external process
  • Calling any other functions with side-effects
  • Making Asynchronous Data Calls
  • But what if we have non-primitive data types?

React.PureComponent and React.memo do not work with non-primitive types. The reason is that non-primitive types can't be directly compared by their value. An example of this would be:

a = {key: 10};b = {key: 10};
c = a;
a === b //returns false
a === c //returns true

The example above shows how an object can’t be equal to another object just because its value looks the same. Non-primitive types can only be equal to each other when they are pointing to the same location in memory, like the variables c and a are, in the example above.

  • How do we prevent rerendering, when using non-primitive data types?

In a pure component, you can not override the shouldComponentUpdate(). Here the solution would be to use extend the class with the normal React.Component and use a shouldComponentUpdate()

But in a memoized component we do have the option to control the logic of the memo, by using the areEqual function.

Another way of solving this problem is through useCallback hook, which is available for the functional components. By this hook, we can store an instance of a function, object or array and only change it when some dependencies change.

An example of this can be found in the video below:

3. Use the useReducer hook

The useReducer hook is great for preventing a rerender on a state change. The reason why the useReducer is better at reducing rerenders compared to useState is that:

  • If the useReducer Hook returns the same value as the current state, React will bail out without rendering the children or firing effects, because it uses the Object.is comparison algorithm.

4. Prevent unnecessary remounting

Unnecessary remounting means that the component completely unmounts ad mounts again. remounting much worse than rerendering. Because when we are remounting, the component not only rerenders but will also reset its states and recompute its lifecycle methods.

The two most common causes of unnecessary unmounting are:

  • Not using event.preventDefault()

event.preventDefault() is used to prevent the default event actions in the individual browsers. In some browsers, there is a default of reloading the page when a click event fires. This causes the entire page to remount.

  • Unoptimized conditional rendering
Unoptimized conditional rendering

The code above shows a clear example of bad conditional rendering. Whenever the state changes, both the <HeaderComponent/> and the <ContentComponent/> remount, for no reason.

If the same exact same component gets rendered on both sides of a ternary operator, then it is probably a sign of bad condition rendering.

A better way of creating the solution shown above would be to move out the <HeaderComponent/> and the <ContentComponent/> from the conditional render, as shown below.

Optimized conditional rendering

5. Optimized useEffect dependencies

Last but not least is the case of bad useEffect() dependencies. The useEffect oftentimes involves some state manipulation, which causes rerendering. Because of that, we have to be specific, so that we don’t run the useEffect when it's not necessary.

The two main ways to prevent running the useEffect when it's not necessary are to be specific and use memoization.

  • Be specific
useffect({
// do something
}, [someObjectState])

If we need a certain property of the someObject we could do as shown above.

The problem with the example shown above is that every time any of the someObject properties change, it runs the useEffect.

A better way to write this effect would be to do as shown below, where we specify a property of the object:

useffect({
// do something
}, [someObjectState.someProperty])
  • Use memoization

Another technique is to use a memoized value, with the useMemo hook. This memoized value only updates when its dependencies change, therefore reducing the number of changes of the value. As shown below:

const memoizedRoomSiz = useMemo(() => {
return length * width
},[length])
useffect({
// do something
}, [memoizedRoomSiz])

To read more about memoization I would recommend you to read this story from Michael Krasnov:

Conclusion

Although React is a good and performant library it still has its downsides. That especially starts being noticeable as your application starts to scale. That is why we have to be aware of our rerenders as early as possible because it can quickly turn into a mess. Remember to React Developer Tools profiler, to help you spot rerenders that are not visible to the eye. But if you make:

  • Look out for infinite loops
  • Use dynamic programming
  • Consider using the reducer when your state often gets set to its current value
  • Look out for necessary remounting
  • Optimize your useEffect dependencies

… a part of your daily workflow you will be far ahead.

--

--

Vitaliysteffensen

Web development expert writing about the development of tomorrow. 👉 Follow and learn how to step up your web development! 💻