Ohans Emmanuel Author, Understanding Redux. I Love God. I Love GF a little too much šŸ’•šŸ¤£ http://thereduxjsbooks.com

Death by a thousand cuts: A checklist for eliminating common React performance issues

A pragmatic, step-by-step guide to eliminating common React performance issues.

8 min read 2414

Have you ever wondered how to make your React applications faster?

Yes?

How about having a checklist for eliminating common React performance issues?

Well, you are in the right place.

In this article, Iā€™ll walk you through a pragmatic, step-by-step guide to eliminating common React performance issues.

First, Iā€™ll show you the problems and then provide solutions to them. In doing this, youā€™ll be able to carry over the same concepts to your real-world projects.

The goal of this article is not to be a lengthy essay, rather, Iā€™ll discuss quick tips you can start using in your applications today.

Letā€™s get started!

The sample project

To make this article as pragmatic as possible, Iā€™ll walk you through various use cases while working on a single React application.

I call this appĀ Cardie.

The Cardie app.

Hereā€™s theĀ GitHub repoĀ if you want to follow along.

Cardie is a simple application. All it does is display the profile information of a user. Commonly known as a user profile card.

The user details being displayed.

It also includes the added functionality of being able to change the userā€™s profession with a button click.

Cardie in action šŸ™‚

Upon clicking the button at the bottom of the app, the userā€™s profession is changed.

While you may be quick to laugh this off as a simple application, and nowhere near a real-world app, you may be surprised to know that the knowledge gained from hunting down performance issues with this example application applies to whatever real-world app you build.

So, stay calm and enjoy the article.

Here comes the checklist!

1. Identify wasted renders

Identifying wasted renders in your React applications is the perfect start to identifying most performance issues.

There are a couple different ways to approach this, but the simplest method is to toggle on the ā€œhighlight updatesā€ option in the React DevTools.

How to enable the highlight update toggle in React DevTools.

While interacting with the app, updates are highlighted on the screen with a green flash.

The green flash shows the components in the app that are re-rendered by React under the hood.

With Cardie, upon changing the userā€™s profession, it appears that the entire parent componentĀ Appis re-rendered.

Note the green flash around the user card.

That doesnā€™t look right.

The app works, but thereā€™s really no reason for the entire component to be re-rendered when the change to be updated is in one small bit.

The actual update happens in a small part of the app.

A more ideal highlighted update should look like this:

Note how the highlighted update is contained within the small updatedĀ region.

In more complex applications, the impact of a wasted render may be huge! The re-rendered component may be large enough to promote performance concerns.

Having seen the problem, are there any solutions to this?

2. Extract frequently updated regions into isolated components

Once youā€™ve visually noted wasted renders in your application, a good place to start is to attempt to break up your component tree to support regions updated frequently.

Let me show you what I mean.

In Cardie, theĀ App component is connected to the Redux store via the connect function fromĀ react-redux. From the store, it receives the props name,Ā location,Ā likes, and description.

<App/> receives the props it needs directly from the Redux store.

TheĀ descriptionĀ props define the current profession of the user.

Essentially, whatā€™s happening is that whenever the user profession is changed by clicking the button, theĀ descriptionĀ prop is changed. This change in props then causes theĀ AppĀ component to be re-rendered entirely.

If you remember from React 101, whenever theĀ propsĀ orĀ stateĀ of a component changes, a re-render is triggered.

A React component renders a tree of elements. These elements are defined via props and state. If the props or state values changes, the tree of elements is re-rendered. This results in a newĀ tree.

Instead of allowing theĀ AppĀ component to re-render pointlessly, what if we localized the elements being updated to a specific React component?

For example, we could create a new component calledĀ ProfessionĀ which renders its own DOM elements.

In this case, theĀ Profession component will render the description of the userā€™s profession, e.g., ā€œI am a Coderā€.

The <Profession/> component will be rendered in the <App/> component.

The component tree would now look like this:

The <App/> component renders the elements it renders plus the <Profession/> component.

Whatā€™s also important to note here is that instead of lettingĀ <App/>Ā worry about theĀ professionĀ prop, thatā€™ll now be a concern for theĀ <Profession/>component.

The profession will now be retrieved directly from the redux store by the <Profession/> component.

Whether you useĀ reduxĀ or not, the point here is thatĀ AppĀ no longer has to be re-rendered owing to changes from theĀ professionĀ prop. Instead, theĀ <Profession/>Ā component will be.

Upon completing this refactor, we have an ideal update highlighted as seen below:

Note how the highlighted updates is contained within <Profession />.

To view the code change, please see theĀ isolated-component branchĀ from the repo.

3. Use pure components when appropriate

Any React resource talking about performance will very likely mention pure components.

However, how do you know when to properly useĀ pure components?

Well, it is true that you could make every component a pure component, but remember thereā€™s a reason why that isnā€™t the case out of the box ā€” hence, the shouldComponentUpdateĀ function.

The premise of a pure component is that the component only re-renders if the propsĀ are different from the previous props and state.

An easy way to implement a pure component is to useĀ React.PureComponent as opposed to the default React.Component.

Using React.PureComponent as opposed to React.Component.

To illustrate this specific use case inĀ Cardie, letā€™s break down the render elements of theĀ ProfessionĀ component into smaller components.

So, this was the content ofĀ ProfessionĀ before now:

const Description = ({ description }) => {
  return (
    <p>
     <span className="faint">I am</span> a {description}
    </p>
  );
}

Hereā€™s what weā€™ll change it to:

const Description = ({ description }) => {
  return (
    <p>
      <I />
      <Am />
      <A />
      <Profession profession={description} />
    </p>
  );
};

Now, theĀ DescriptionĀ component renders 4 children components.

Note that the Description component takes in aĀ professionĀ prop. However, it passes on this prop to theĀ ProfessionĀ component. Technically, none of the other 3 components care about thisĀ professionĀ prop.

The contents of these new components are super simple. For example, theĀ <I />Ā component just returns aĀ spanĀ element with an ā€œIā€ text:Ā span >I </span>

If the application runs, the result is just the same. The app works.

Whatā€™s interesting is that upon a change to theĀ descriptionĀ prop, every child component ofĀ ProfessionĀ is also re-rendered.

Once a new prop value is passed into the Description component, all the child components also re-render.

I added a few logs in theĀ renderĀ methods of each child component – and as you can see they were indeed all re-rendered.

You may also view the highlighted updates using the react dev tools.

Note how there are green flashes around each of the words “I,” “am,” and “a.”

This behavior is expected. Whenever a component has eitherĀ propsĀ orĀ state changed, the tree of elements it renders is recomputed. This is synonymous to a re-render.

In this particular example, youā€™ll agree with me that if really makes no sense forĀ <I/>,Ā <Am/>Ā andĀ <A/>Ā child components to be re-rendered. Yes, theĀ propsĀ in the parent element,Ā <Description/>Ā changed, but if this were a sufficiently large application, this behavior may pose some performance threats.

What if we made these child components pure components?

Consider theĀ <I/>Ā component:

import React, {Component, PureComponent} from "react"

//before 
class I extends Component {
  render() {
    return <span className="faint">I </span>;
}

//after 
class I extends PureComponent {
  render() {
    return <span className="faint">I </span>;
}

By implication, React is informed under the hood so that if the prop values for these child components arenā€™t changed, thereā€™s no need to re-render them.

Yes, do not re-render them even when the parent element has a change in its props!

Upon inspecting the highlighted updates after the refactor, you can see that the child components are no longer re-rendered. Just theĀ ProfessionĀ component whoseĀ propĀ actually changes is re-rendered.

There are no flashes around the words ā€œIā€, ā€œam,ā€ and ā€œa.ā€ Just the overall container, and the profession flashes.

In a larger application, you may find immense performance optimizations by just making certain components pure components.

To view the code change, please see theĀ pure-component branchĀ from the repo.

4. Avoid passing new objects as props

Remember again that whenever theĀ propsĀ for a component changes, a re-render happens.

If the props or state values changes, the tree of elements is re-rendered. This results in a new elementĀ tree.

What if theĀ propsĀ for your component didnā€™t change but React thinks it did change?

Well, thereā€™ll also be a re-render! But isnā€™t that weird?

This seemingly weird behavior happens because of how Javascript works & how React handles its comparison between old and new prop values.

Let me show you an example.

Hereā€™s the content for theĀ DescriptionĀ component:

const Description = ({ description }) => {
  return (
    <p>
       <I />
       <Am />
       <A />
       <Profession profession={description} />
    </p>
  );
};

Now, we will refactor theĀ IĀ component to take in a certainĀ iĀ prop. This will be an object of the form:

const i = { 
  value: "i"
};

Whatever value property is present inĀ iĀ will be set as the value in theĀ I component.

class I extends PureComponent {
  render() {
    return <span className="faint">{this.props.i.value} </span>;
  }
}

In theĀ DescriptionĀ component, theĀ iĀ prop is created and passed in as shown below:

class Description extends Component {
  render() {
    const i = {
      value: "i"
    };
    return (
      <p>
            <I i={i} />
        <Am />
        <A />
        <Profession profession={this.props.description} />
      </p>
    );
  }
}

Bear with me while I explain this.

This is perfectly correct code, and it works fine ā€” but there is one problem.

Even thoughĀ IĀ is a pure component, now it re-renders whenever the profession of the user is changed!

On clicking the button, the logs show that both <I/> and <Profession/> are re-rendered. Remember thereā€™s been no actual props change in `<I/>`. Why the re-render?

But why?

As soon as theĀ DescriptionĀ component receives the new props, theĀ render function is called to create its element tree.

Upon invoking the render function, it creates a new iĀ constant:

const i = { 
  value: "i"
};

When React gets to evaluate the line <I i={i} />, it sees the propsĀ i as a different prop, a new object ā€” hence the re-render.

If you remember from React 101, React does a shallow comparison between the previous and next props.

Scalar values such as strings and numbers are compared by value. Objects are compared by reference.

By implication, even though the constantĀ iĀ has the same value across re-renders, the reference is not the same. The position in memory isnā€™t.

Itā€™s a newly created object with every single render call. For this reason, theĀ propĀ value passed toĀ <I/> is regarded as ā€œnew,ā€ consequently producing a re-render.

In bigger applications, this can lead to a wasted render and potential performance pitfalls.

Avoid this.

This applies to everyĀ prop, including event handlers.

If you can avoid it, you shouldnā€™t do this:

... 
render() {
  <div onClick={() => {//do something here}}
}
...

Youā€™re creating a new function object every time within render. Better to do this:

...
handleClick:() ={
}
render() {
  <div onClick={this.handleClick}
}
...

Got that?

In the same vein, we may refactor the prop sent toĀ <I />Ā as shown below:

class Description extends Component {
  i = {
    value: "i"
  };
  render() {
    return (
      <p>
       <I i={this.i} />
       <Am /> 
       <A />
       <Profession profession={this.props.description} />
      </p>
    );
  }
}

Now, the reference is always the same,Ā this.i.

A new object isnā€™t created at render time.

To view the code change, please see theĀ new-objects branchĀ from the repo.

5. Use the production build

When deploying to production, always use the production build. This is a very simple but great practice.

The ā€œdevelopment buildā€ warning you get with the React DevTools in development.

If you have bootstrapped your application withĀ create-react-app to run the production build, use the commandĀ npm run build.

This will yield bundle-optimized files for production.

6. Employ code splitting

When you bundle your application, you likely have the entire application bundled in one large chunk. The problem with this is that as your app grows, so does the bundle.

Once users visit the site, they are sent a large chunk of code for the entire app.

Code splitting advocates that instead of sending this large chunk of code to users at once, you may dynamically send chunks to users when they need it.

A common example is with route-based code splitting. In this method, the code is split into chunks based on the routes in the application.

The /home route gets a small chunk of code, so does the /aboutĀ route.

Another approach is component-based code splitting. In this method, if a component is currently not displayed to the user, its code may be delayed from being sent to the user.

Whichever method you stick to, it is important to understand the trade-offs and not degrade the user experience of your application.

Code splitting is great, and it can improve your applicationā€™s performance.

I have taken a conceptual approach to explain code splitting. If you want more technical grounds, please have a look at the officialĀ React docs. They do a decent job at explaining the concept technically.

Conclusion

Now youā€™ve got a decent checklist for tracking and fixing common performance issues in React apps. Go build some fast apps!

Plug: , a DVR for web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Ohans Emmanuel Author, Understanding Redux. I Love God. I Love GF a little too much šŸ’•šŸ¤£ http://thereduxjsbooks.com

Leave a Reply