Optimizing React Performance with Stateless Components

Peter Bengtsson
Share

A React logo made of bones, on display in a museum alongside dinosaur fossils. A look at the evolution of stateless components.

This story is about stateless components. This means components that don’t have any this.state = { ... } calls in them. They only deal with incoming “props” and sub-components.

First, the Super Basics

import React, { Component } from 'react'

class User extends Component {
  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3
        style={{fontStyle: highlighted ? 'italic' : 'normal'}}
        onClick={event => {
          userSelected()
        }}
        >{name}</h3>
    </div>
  }
}

Editor’s Note: We’re trying out CodeSandbox for the demos in this article.
Let us know what you think!

Yay! It works. It’s really basic but sets up the example.

Things to note:

  • It’s stateless. No this.state = { ... }.
  • The console.log is there so you can get insight it being used. In particular, when you do performance optimization, you’ll want to avoid unnecessary re-renders when the props haven’t actually changed.
  • The event handler there is “inline”. This is convenient syntax because the code for it is close to the element it handles, plus this syntax means you don’t have to do any .bind(this) sit-ups.
  • With inline functions like that, there is a small performance penalty since the function has to be created on every render. More about this point later.

It’s a Presentational Component

We realize now that the component above is not only stateless, it’s actually what Dan Abramov calls a presentational component. It’s just a name but basically, it’s lightweight, yields some HTML/DOM, and doesn’t mess around with any state-data.

So we can make it a function! Yay! That not only feels “hip”, but it also makes it less scary because it’s easier to reason about. It gets inputs and, independent of the environment, always returns the same output. Granted, it “calls back” since one of the props is a callable function.

So, let’s re-write it:

const User = ({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3
      style={{fontStyle: highlighted ? 'italic' : 'normal'}}
      onClick={event => {
        userSelected()
      }}>{name}</h3>
  </div>
}

Doesn’t that feel great? It feels like pure JavaScript and something you can write without having to think about the framework you’re using.

It Keeps Re-rendering, They Say :(

Suppose our little User is used in a component that has state which changes over time. But the state doesn’t affect our component. For example, something like this:

import React, { Component } from 'react'

class Users extends Component {
  constructor(props) {
    super(props)
    this.state = {
      otherData: null,
      users: [{name: 'John Doe', highlighted: false}]
    }
  }

  async componentDidMount() {
    try {
      let response = await fetch('https://api.github.com')
      let data = await response.json()
      this.setState({otherData: data})
    } catch(err) {
      throw err
    }
  }

  toggleUserHighlight(user) {
    this.setState(prevState => {
      users: prevState.users.map(u => {
        if (u.name === user.name) {
          u.highlighted = !u.highlighted
        }
        return u
      })
    })
  }

  render() {
    return <div>
      <h1>Users</h1>
      {
        this.state.users.map(user => {
          return <User
            name={user.name}
            highlighted={user.highlighted}
            userSelected={() => {
              this.toggleUserHighlight(user)
            }}/>
         })
      }
    </div>
  }
}

If you run this, you’ll notice that our little component gets re-rendered even though nothing has changed! It’s not a big deal right now, but in a real application components tend to grow and grow in complexity and each unnecessary re-render causes the site to be slower.

If you were to debug this app now with react-addons-perf I’m sure you’d find that time is wasted rendering Users->User. Oh no! What to do?!

Everything seems to point to the fact that we need to use shouldComponentUpdate to override how React considers the props to be different when we’re certain they’re not. To add a React life cycle hook, the component needs to go be a class. Sigh. So we go back to the original class-based implementation and add the new lifecycle hook method:

Back to Being a Class Component

import React, { Component } from 'react'

class User extends Component {

  shouldComponentUpdate(nextProps) {
    // Because we KNOW that only these props would change the output
    // of this component.
    return nextProps.name !== this.props.name || nextProps.highlighted !== this.props.highlighted
  }

  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3
        style={{fontStyle: highlighted ? 'italic' : 'normal'}}
        onClick={event => {
          userSelected()
        }}
        >{name}</h3>
    </div>
  }
}

Note the new addition of the shouldComponentUpdate method. This is kinda ugly. Not only can we no longer use a function, we also have to manually list the props that could change. This involves a bold assumption that the userSelected function prop doesn’t change. It’s unlikely, but something to watch out for.

But do note that this only renders once! Even after the containing App component re-renders. So, that’s good for performance. But can we do it better?

What About React.PureComponent?

As of React 15.3, there’s a new base class for components. It’s called PureComponent and it has a built-in shouldComponentUpdate method that does a “shallow equal” comparison of every prop. Great! If we use this we can throw away our custom shouldComponentUpdate method which had to list specific props.

import React, { PureComponent } from 'react'

class User extends PureComponent {

  render() {
    const { name, highlighted, userSelected } = this.props
    console.log('Hey User is being rendered for', [name, highlighted])
    return <div>
      <h3
        style={{fontStyle: highlighted ? 'italic' : 'normal'}}
        onClick={event => {
          userSelected()
        }}
        >{name}</h3>
    </div>
  }
}

Try it out and you’ll be disappointed. It re-renders every time. Why?! The answer is because the function userSelected is recreated every time in App‘s render method. That means that when the PureComponent based component calls its own shouldComponentUpdate() it returns true because the function is always different since it’s created each time.

Generally the solution to that is to bind the function in the containing component’s constructor. First of all, if we were to do that it means we’d have to type the method name 5 times (whereas before it was 1 times):

  • this.userSelected = this.userSelected.bind(this) (in the constructor)
  • userSelected() { (as the method definition itself)
  • <User userSelected={this.userSelected} ... (when defining where to render the User component)

Another problem is that, as you can see, when actually executing that userSelected method it relies on a closure. In particular that relies on the scoped variable user from the this.state.users.map() iterator.

Admittedly, there is a solution to that and that’s to first bind the userSelected method to this and then when calling that method (from within the child component) pass the user (or its name) back. Here is one such solution.

recompose to the Rescue!

First, to iterate, what we want:

  1. Writing functional components feels nicer because they’re functions. That immediately tells the code-reader that it doesn’t hold any state. They’re easy to reason about from a unit testing point of view. And they feel less verbose and purer JavaScript (with JSX of course).
  2. We’re too lazy to bind all the methods that get passed into child components. Granted, if the methods are complex it might be nice to refactor them out instead of creating them on-the-fly. Creating methods on-the-fly means we can write its code right near where they get used and we don’t have to give them a name and mention them 5 times in 3 different places.
  3. The child components should never re-render unless the props to them change. It might not matter for tiny snappy ones but for real-world applications when you have lots and lots of these all that excess rendering burns CPU when it can be avoided.

(Actually, what we ideally want is that components are only rendered once. Why can’t React solve this for us? Then there’d be 90% fewer blog posts about “How To Make React Fast”.)

recompose is “a React utility belt for function components and higher-order components. Think of it like lodash for React.” according to the documentation. There’s a lot to explore in this library, but right now we want to render our functional components without them being re-rendered when props don’t change.

Our first attempt at re-writing it back to a functional component but with recompose.pure looks like this:

import React from 'react'
import { pure } from 'recompose'

const User = pure(({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3
      style={{fontStyle: highlighted ? 'italic' : 'normal'}}
      onClick={event => {
        userSelected()
      }}>{name}</h3>
  </div>
})

export default User

As you might notice, if you run this, the User component still re-renders even though the props (the name and highlighted keys) don’t change.

Let’s take it up one notch. Instead of using recompose.pure we’ll use recompose.onlyUpdateForKeys which is a version of recompose.pure, but you specify the prop keys to focus on explicitly:

import React from 'react'
import { onlyUpdateForKeys } from 'recompose'

const User = onlyUpdateForKeys(['name', 'highlighted'])(({ name, highlighted, userSelected }) => {
  console.log('Hey User is being rendered for', [name, highlighted])
  return <div>
    <h3
      style={{fontStyle: highlighted ? 'italic' : 'normal'}}
      onClick={event => {
        userSelected()
      }}>{name}</h3>
  </div>
})

export default User

When you run that you’ll notice that it only ever updates if props name or highlighted change. If it the parent component re-renders, the User component doesn’t.

Hurrah! We have found the gold!

Discussion

First of all, ask yourself if it’s worth performance optimizing your components. Perhaps it’s more work than it’s worth. Your components should be light anyway and perhaps you can move any expensive computation out of components and either move them out into memoizable functions outside or perhaps you can reorganize your components so that you don’t waste rendering components when certain data isn’t available anyway. For example, in this case, you might not want to render the User component until after that fetch has finished.

It’s not a bad solution to write code the most convenient way for you, then launch your thing and then, from there, iterate to make it more performant. In this case, to make things performant you need to rewrite the functional component definition from:

const MyComp = (arg1, arg2) => {
...
}

…to…

const MyComp = pure((arg1, arg2) => {
...
})

Ideally, instead of showing ways to hack around things, the best solution to all of this, would be a new patch to React that is a vast improvement to shallowEqual that is able to “automagically” decipher that what’s being passed in and compared is a function and just because it’s not equal doesn’t mean it’s actually different.

Admission! There is a middle-ground alternative to having to mess with binding methods in constructors and the inline functions that are re-created every time. And it’s Public Class Fields. It’s a stage-2 feature in Babel so it’s very likely your setup supports it. For example, here’s a fork using it which is not only shorter but it now also means we don’t need to manually list all non-function props. This solution has to forgo the closure. Still, though, it’s good to understand and be aware of recompose.onlyUpdateForKeys when the need calls.

For more on React, check out our course React The ES6 Way.

This article was peer reviewed by Jack Franklin. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!