17

Avoiding Unnecessary Renders in React

You’re probably really tired of me saying this, but fast DOM performance is one of the biggest feathers in React’s cap. That doesn’t mean you get all that great performance for free, though. While React handles a lot of the heavy lifting, you should consciously take certain steps to ensure that your app isn’t doing unnecessary work and slowing things down. One of the biggest steps involves making sure each component’s render method is called only when it absolutely has to be. In the next few sections, we’ll look at why that’s a problem and what you can do about it.

About the render Method

The render method’s official job description is pretty simple. It just needs to show up on each component and help generate the JSX to return to whatever parent component called it. If we had to loosely describe the full workflow from components on one end and a fully finished app on the other end, it would look as follows:

A figure shows the workflow from the components to the finished app.

You have your finished app on one side. You have the components that make up the app on the other. Inside all these components, you see render methods returning bits and chunks of JSX getting combined with more bits and chunks of JSX from other components. This process repeats until you get the final JSX at the root of the component hierarchy where we have our ReactDOM.render call. From there, the React Magic happens that turns all this JSX into the appropriate HTML/CSS/JS to display in the browser.

Now that you have a very basic idea of how React works, let’s get back into the weeds where our components and their render methods live. In all the React code you’ve written so far, you might also have noticed that you never had to explicitly call the render method on any component. That just sort of happened automatically. Let’s get more precise here. Three things cause a render method to automatically get called:

1. A prop that lives on your component gets updated.

2. A state property that lives on your component gets updated.

3. A parent component’s render method gets called.

All three of these cases seem like good examples of when we want our component’s render method to be automatically called. After all, all three of these cases could cause your visual state to change, right?

The answer is, well, it depends! Very often, components find themselves being forced to re-render even though the prop or state that is changing has absolutely nothing to do with them. In some situations, a parent component is correctly rendering or re-rendering, but that is localized to just that component. There’s no need to ask the child components to re-render for something that doesn’t affect them.

Now, I might be painting an alarming picture of unnecessary work that has been going on right under our noses. One point to keep in mind is that a render method being called is not the same thing as the DOM ultimately getting updated. React takes a few additional steps in which the DOM is diffed (that is, the previous version is compared with the new/current version) to truly see if any changes need to be represented. All of these “few additional steps” means work, and more complex apps with a lot of components will face many instances that will start to add up. Some of this is additional work done by React’s internals. Some of it are just important things we do in our render methods; we often have a lot of code there to help generate the appropriate JSX. Rarely does our render method return a static piece of JSX with no evaluation or calculation happening, so minimizing unnecessary render calls is a good thing.

Optimizing render Calls

Now that we’ve looked at the problem, let’s examine some approaches we can use to ensure that we’re calling a component’s render method only when absolutely necessary. The following sections walk you through this.

Getting an Example Going

To help make sense of this, we’re going to look at an example. It’s not just any example, either. We’ll revisit our sliding menu that we created earlier. If you have it handy, go ahead and open it in your code editor.

If you don’t have the project handy, that’s okay. Use create-react-app to create a new React project and overwrite everything in your src and public folders with the contents from the Sliding Menu Github repo:

A screenshot shows the contents of the GitHub repo.

When you have the sliding menu project ready, run it in your browser to make sure that everything works—or still works.

If you haven’t completed the sliding menu from the previous chapter, I highly encourage you to do so. Having the working project handy is all fine and good, but knowing how the code works and understanding some of the choices we made during implementation is important. You can certainly follow along without understanding that chapter, but don’t say I didn’t warn you if some of the code you’re about to see seems a bit out of place.

Looking at our example, to reuse a graphic you’ve already seen, the component hierarchy for our sliding menu app looks as follows:

The MenuContainer component is indicated to comprise the MenuButton (the blue circle) and the Menu (the menu options listed below the circle).

At the root is MenuContainer, and it has two children: MenuButton and Menu. Although it’s not shown in the diagram, there’s a ReactDOM.render call in index.js that exposes our MenuContainer to the DOM:

When the button rendered by MenuButton is clicked, we set a Boolean state property (called visible) in MenuContainer to true. This state property change triggers our Menu component to update a class value that activates the appropriate CSS to slide our menu in. Clicking anywhere in the menu dismisses the menu by undoing what was done via setting the state property in MenuContainer to false.

Seeing the render Calls

The first thing we want to do is see the render calls being made. You can do this in many ways. You can set a break point in your code and inspect the results using your browser’s developer tools. You can install the React Developer Tools add-on (for Chrome or Firefox) from https://github.com/facebook/react-devtools and inspect each component. You can also take a very simple approach and insert console.log statements inside the render methods you’re interested in.

Because we have only three components in our sliding menu example, the console.log approach is an easy one that we’ll use for now. In your code editor, open MenuContainer.js, MenuButton.js, and Menu.js and scroll down to each component’s respective render method. At the very top of this method, we’re going to add a console.log call.

In MenuContainer.js, add the following highlighted line:

Let’s do something similar in MenuButton.js:

Lastly, add the following highlighted line in Menu.js:

Once you have added these three lines, run your app in your browser. Once the app is up and running, bring up your browser’s developer tools and take a look at what is printed in the console:

A screenshot shows the information displayed in the console tab of the developer tools.

You might see warnings and other stuff displayed, but look for the output of the console.log statements you added. When you first run your app, you’ll see that all three of our components have their respective render method getting called. This is expected because it’s the first time your app is being loaded.

With your console still open, go ahead and click the blue button to bring up the menu. Then take a look at your console. You’ll see the following new items (highlighted in green):

Rendering: MenuContainer

Rendering: MenuButton

Rendering: Menu

clicked

Rendering: MenuContainer

Rendering: MenuButton

Rendering: Menu

When the handleMouseDown event handler gets called, we print the text clicked to the console. This isn’t important for what we’re doing right now, but it does provide a nice separation between our series of render calls. With that said, notice that displaying our menu results in all three of our components’ render methods getting called. Click the menu to dismiss it. You’ll see that all three render methods get called again. That doesn’t seem right, does it?

Because we’re toggling a prop on Menu and our state is stored on MenuContainer, it makes sense for those two components’ render methods to be called…for now. But, why is our MenuButton component’s render method getting called every single time?

Looking at MenuContainer’s render call, we are calling our Menu component and passing in a prop whose value will never change:

The value of our handleMouseDown method doesn’t change each time our menu is opened or closed. This is because our MenuContainer (a.k.a. the MenuButton’s parent) has its render method called. If a parent’s render method gets called, all the child components’ render methods get called as well. If you’re keeping score, this is reason #3 we listed a few sections ago when talking about what causes a render method to be called automatically.

So what options do we have for stopping our MenuButton component’s render method from being unnecessarily called? As it turns out, we have two.

Overriding a Component Update

A while ago, we looked at the various lifecycle methods React provides. One of them is shouldComponentUpdate. This method is called just before a render call is made, and you can block the render method from being called by having the shouldComponentUpdate method return false. Here we’re going to use the shouldComponentUpdate method to do just that.

Inside the MenuButton component, add the following highlighted lines:

Refresh your app to test your code. Pay attention to the console and see what gets printed when you’re showing and hiding a menu. Your output when your page loads and the menu is displayed for the first time now appears as follows:

Rendering: MenuContainer

Rendering: MenuButton

Rendering: Menu

clicked

Rendering: MenuContainer

Rendering: Menu

Notice that our MenuButton component’s render method isn’t called. That’s great. Before we celebrate too much, though, we’ve really taken a hammer to our problem by always returning false when shouldComponentUpdate gets called. While that works for what we are doing, let’s be a bit more careful to ensure that we aren’t accidentally preventing valid updates in the future if we eventually modify MenuButton and how it gets used.

When you look at the shouldComponentUpdate method’s signature, you can see that two arguments are passed in. One is for the next prop value, and the other is for the next state value. We can use these arguments to compare the present with the future and act a bit more intelligently about whether we allow our render call to be made. In the case of MenuButton, the only prop we’re passing in is for the value of handleMouseDown. We can check to ensure that this value doesn’t change by modifying the shouldComponentUpdate method, as follows:

This code ensures that we don’t unnecessarily call render if the value of handleMouseDown stays the same. If the value of handleMouseDown changed, we could properly return a value of true to allow the render call to be made. You can use other criteria to specify whether the component’s render method should get called, and what you do depends entirely on the component in question. Feel free to get creative, if you need to.

Using PureComponent

It’s a common occurrence to have components forced to re-render despite not having relevant prop or state changes. Our MenuButton example is just one such occurrence. The solution is to call shouldComponentUpdate and check whether any prop or state changes have taken place. To avoid having to make this check all the time, a special kind of component can handle this checking automatically for you. That component is PureComponent.

Until now, all of our components have been based on Component:

To base our components off PureComponent, all you have to do is this:

That’s pretty much it. Your component will now be extra careful about calling render only when it determines that a change to either the prop or state has actually been made. To see this for yourself, you can change MenuButton to be a PureComponent instead of just a Component.

In MenuButton.js, first delete the shouldComponentUpdate method; you don’t need it anymore. Then make the following two highlighted changes:

We first import the necessary code from the React library to make PureComponent work. Next, we extend our MenuButton from PureComponent. That’s it. If you test your app now and inspect the console after displaying the menu, you’ll see that our MenuButton component’s render method doesn’t get called when your menu decides to show up (or disappear).

Why Not Always Use PureComponent?

The PureComponent seems pretty awesome, right? Why don’t we just use it always and ditch Component altogether? We probably should! With that said, there are a few reasons you might want to stick with Component.

First, PureComponent performs what’s known as a shallow comparison. It isn’t a comprehensive check of everything that might have changed in your props or state between calls to re-render. For many cases, that’s okay. For other cases, that might not be. Keep that in mind when using PureComponent. You might find that you need to write your own shouldComponentUpdate and handle the updating logic manually. You can’t use PureComponent and specify shouldComponentUpdate at the same time, although that makes a nice try!

Beyond the comparison logic, the bigger problem with using PureComponent is performance. Having each of your components check to see if props or state have changed, even if it’s a shallow check, takes up computation time. Remember, these checks happen every time your component decides to re-render or is asked to re-render by a parent. For complex UIs, that could happen frequently without you even realizing it.

TL;DR: This should probably have been mentioned at the top of this note, but what are you going to do? Basically, it’s fine to use PureComponent instead of Component. Just be aware of the two (minor) side effects.

Conclusion

Ensuring that your app is performant requires constant vigilance. Profile your app’s performance frequently, and definitely do so each time you make a code change with the goal of optimizing performance. Each performance optimization you make brings complexity that just adds to your (or your team’s) overhead of maintaining the code and making fixes in it for the lifetime of your app. Be conscious and don’t overoptimize. If your app works really well on the devices and browsers you’re targeting (especially the low-end ones), consider your job done. Take a break and don’t do any extra work!

Note: If you run into any issues, ask!

If you have any questions or your code isn’t running like you expect, don’t hesitate to ask! Post on the forums at https://forum.kirupa.com and get help from some of the friendliest and most knowledgeable people the Internet has ever brought together!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset