Tutorial

5 Tips to Improve the Performance of Your React Apps

Published on March 8, 2020
author

William Le

5 Tips to Improve the Performance of Your React Apps

Has your React app been feeling kinda sluggish? Are you afraid of turning on the “paint flash” in Chrome DevTools because of what you might see? Try out these 5 performance tips!

This article contain 5 performance tips for React development. You can use this table of contents to navigate quickly around this article.

Jump to Tips

Use memo and PureComponent

Consider this simplistic React app below. Do you think that <ComponentB> will re-render if only props.propA changes value?

import React from 'react';

const MyApp = (props) => {
  return (
    <div>
      <ComponentA propA={props.propA}/>
      <ComponentB propB={props.propB}/>
    </div>
  );
};

const ComponentA = (props) => {
  return <div>{props.propA}</div>
};

const ComponentB = (props) => {
  return <div>{props.propB}</div>
};

The answer is YES! This is because MyApp is actually re-evaluated (or re-rendered 😏) and <ComponentB> is in there. So even though its own props didn’t change, it’s parent component causes it to re-render.

This concept also applies to render methods for Class-based React components.

The authors of React perceived this wouldn’t always be the desired result, and there would be some easy performance gains by simply comparing old and new props before re-rendering… this is essentially what React.memo and React.PureComponent are designed to do!


Let’s use memo with functional components, (we’ll look at Class-based components after):

import React, { memo } from 'react';

// 🙅‍♀️
const ComponentB = (props) => {
  return <div>{props.propB}</div>
};

// 🙆‍♂️
const ComponentB = memo((props) => {
  return <div>{props.propB}</div>
});

That’s it! You just need to wrap <ComponentB> with a memo() function. Now it will only re-render when propB actually changes value regardless of how many times its parent re-renders!


Let’s look at PureComponent. It’s essentially equivalent to memo, but it’s for class-based components.

import React, { Component, PureComponent } from 'react';

// 🙅‍♀️
class ComponentB extends Component {
  render() {
    return <div>{this.props.propB}</div>
  }
}

// 🙆‍♂️
class ComponentB extends PureComponent {
  render() {
    return <div>{this.props.propB}</div>
  }
}

These performance gains are almost too easy! You might wonder why React components don’t automatically include these internal safeguards against excessive re-rendering.

There’s actually a hidden cost with memo and PureComponent. Since these helpers compare old/new props, this can actually be its own performance bottlenecks. For example, if your props are very large, or you’re passing React components as props, the comparison of old/new props can be costly.

Silver bullets in the world of programming are rare! And memo/PureComponent aren’t an exception. You’ll definitely want to test drive them in a measured, thoughtful way. In some cases, they can surprise you how much computational savings they can yield.

For React Hooks, check out useMemo as a similar way to prevent unnecessary computational work

Avoid Anonymous Functions

Functions that are inside the main body of a component are usually event handlers, or callbacks. In many cases you might be tempted to use anonymous functions for them:

import React from 'react';

function Foo() {
  return (
    <button onClick={() => console.log('boop')}> // 🙅‍♀️
      BOOP
    </button>
  );
}

Since anonymous functions aren’t assigned an identifier (via const/let/var), they aren’t persistent whenever this functional component inevitably gets rendered again. This causes JavaScript to allocate new memory each time this component is re-rendered instead of allocating a single piece of memory only once when using “named functions”:

import React, { useCallback } from 'react';

// Variation 1: naming and placing handler outside the component 
const handleClick = () => console.log('boop');
function Foo() {
  return (
    <button onClick={handleClick}>  // 🙆‍♂️
      BOOP
    </button>
  );
}

// Variation 2: "useCallback"
function Foo() {
  const handleClick = useCallback(() => console.log('boop'), []);
  return (
    <button onClick={handleClick}>  // 🙆‍♂️
      BOOP
    </button>
  );
}

useCallback is another way to avoid the pitfalls of anonymous functions, but it has similar tradeoffs that accompany React.memo that we covered earlier.

With class-based components, the solution is pretty easy and doesn’t really have any downsides. It’s the recommended way to define handlers in React:

import React from 'react';

class Foo extends Component {
  render() {
    return (
      <button onClick={() => console.log('boop')}>  {/* 🙅‍♀️ */}
        BOOP
      </button>
    );
  }
}

class Foo extends Component {
  render() {
    return (
      <button onClick={this.handleClick}>  {/* 🙆‍♂️ */}
        BOOP
      </button>
    );
  }
  handleClick = () => {  // this anonymous function is fine used like this
    console.log('boop');
  }
}

Avoid Object Literals

This performance tip is similar to the previous section about anonymous functions. Object literals don’t have a persistent memory space, so your component will need to allocate a new location in memory whenever the component re-renders:

function ComponentA() {
  return (
    <div>
      HELLO WORLD
      <ComponentB style={{  {/* 🙅‍♀️ */}
        color: 'blue',     
        background: 'gold'
      }}/>
    </div>
  );
}

function ComponentB(props) {
  return (
    <div style={this.props.style}>
      TOP OF THE MORNING TO YA
    </div>
  )
}

Each time <ComponentA> is re-rendered a new object literal has to be “created” in-memory. Additionally, this also means that <ComponentB> is actually receiving a different style object. Using memo and PureComponent won’t even prevent re-renders here 😭

This tip doesn’t apply to style props only, but it’s typically where object literals are unwittingly used in React components.

This can be easily fixed by naming the object (outside of the component’s body of course!):

const myStyle = {  // 🙆‍♂️
  color: 'blue',     
  background: 'gold'
};
function ComponentA() {
  return (
    <div>
      HELLO WORLD
      <ComponentB style={myStyle}/>
    </div>
  );
}

function ComponentB(props) {
  return (
    <div style={this.props.style}>
      TOP OF THE MORNING TO YA
    </div>
  )
}

Use React.lazy and React.Suspense

Part of making your React app fast can be accomplished via code-splitting. This feature was introduced to React v16 with React.lazy and React.Suspense.

If you aren’t aware, the concept of code-splitting is where your JavaScript client source (eg., your React app code) is broken into smaller chunks, and only loads these chunks in a lazy fashion. Without any code-splitting a single bundle could be very large:

- bundle.js (10MB!)

Using code-splitting, this could cause the initial network request for the bundle to be significantly smaller:

- bundle-1.js (5MB)
- bundle-2.js (3MB)
- bundle-3.js (2MB)

The initial network request will “only” need to download 5MB, and it can start showing something interesting to the end user. Imagine a blog website that only needs the header, and footer initially. Once that’s loaded it’ll begin to request the 2nd bundle that contains the actual blog posts. This is a just rudimentary example where code-splitting would be handy. 👏👏👏

How code splitting this done in React?

If you’re using Create React App, it’s already configured for code-splitting, so you can simply use React.lazy and React.Suspense out of the gate! If you’re configuring webpack yourself it should look like this.

Here’s a simple example where lazy and Suspense is implemented:

import React, { lazy, Suspense } from 'react';
import Header from './Header';
import Footer from './Footer';
const BlogPosts = React.lazy(() => import('./BlogPosts'));

function MyBlog() {
  return (
    <div>
      <Header>
      <Suspense fallback={<div>Loading...</div>}>
        <BlogPosts />
      </Suspense>
      <Footer>
    </div>
  );
}

Notice the fallback prop. This will be shown to the user immediately while the 2nd bundle chunk is loaded (eg., <BlogPosts>).

Check out this great article on Code Splitting with React Suspense 🐊

Avoid Frequent Mounting/Unmounting

Many times we’re used to making components disappear using a ternary statement (or something similar):

import React, { Component } from 'react';
import DropdownItems from './DropdownItems';

class Dropdown extends Component {
  state = {
    isOpen: false
  }
  render() {
    <a onClick={this.toggleDropdown}>
      Our Products
      {
        this.state.isOpen
          ? <DropdownItems>
          : null
      }
    </a>
  }
  toggleDropdown = () => {
    this.setState({isOpen: !this.state.isOpen})
  }
}

Since <DropdownItems> is removed from the DOM it can cause a repaint/reflow by the browser. These can be expensive, especially if it causes other HTML elements to shift around.

In order to mitigate this, it’s advisable to avoid completely unmounting components. Instead, you can use certain strategies like setting the CSS opacity to zero, or setting CSS visibility to “none”. This will keep the component in the DOM, while making it effectively disappear without incurring any performance costs.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors
Default avatar
William Le

author

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Become a contributor for community

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

DigitalOcean Documentation

Full documentation for every DigitalOcean product.

Resources for startups and SMBs

The Wave has everything you need to know about building a business, from raising funding to marketing your product.

Get our newsletter

Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

New accounts only. By submitting your email you agree to our Privacy Policy

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Get started for free

Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

*This promotional offer applies to new accounts only.