Optimizing React Performance with Stateless Components

Share this article

A React logo made of bones, on display in a museum alongside dinosaur fossils
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!

Frequently Asked Questions about Optimizing React Performance with Stateless Components

What are the key differences between stateful and stateless components in React?

Stateful components, also known as class components, maintain a memory about the component’s state changes over time. They are responsible for how the component behaves and how it is rendered. On the other hand, stateless components, also known as functional components, do not have their own state. They receive data from the parent component in the form of props and render it. They are mainly responsible for the UI part of components.

How can I optimize the performance of my React application using stateless components?

Stateless components can significantly improve the performance of your React application. Since they don’t manage their own state or lifecycle methods, they have less code which makes them easier to understand and test. They also reduce the amount of memory consumed by the application. To optimize performance, you can convert stateful components to stateless components wherever possible, and use React’s PureComponent to avoid unnecessary re-renders.

What is PureComponent and how does it help in optimizing React performance?

PureComponent is a special type of React component that can help optimize your application’s performance. It implements the shouldComponentUpdate lifecycle method with a shallow prop and state comparison. This means that it only re-renders if there are changes in the state or props, which can significantly reduce unnecessary re-renders and improve performance.

How can I convert a stateful component to a stateless component in React?

Converting a stateful component to a stateless component involves removing any state or lifecycle methods from the component. The component should only receive data through props and render it. Here’s an example:

// Stateful Component
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

// Stateless Component
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

What are the best practices for using stateless components in React?

Some best practices for using stateless components in React include: using them as much as possible to improve performance and readability, keeping them small and focused on a single functionality, and avoiding the use of state or lifecycle methods. It’s also recommended to use destructuring with props for cleaner code.

Can stateless components use lifecycle methods in React?

No, stateless components cannot use lifecycle methods in React. Lifecycle methods are only available to class components. However, with the introduction of Hooks in React 16.8, you can now add state and lifecycle features to functional components.

What are the limitations of stateless components in React?

While stateless components have many benefits, they also have some limitations. They cannot use lifecycle methods or manage their own state. However, these limitations can be overcome with the use of React Hooks.

How do stateless components improve the readability of the code?

Stateless components improve the readability of the code by being concise and clear. They don’t manage their own state or use lifecycle methods, which makes them simpler and easier to understand. They also encourage the use of small, reusable components, which can make the code more organized and easier to maintain.

Can stateless components handle events in React?

Yes, stateless components can handle events in React. Event handlers can be passed as props to the stateless components. Here’s an example:

function ActionLink(props) {
return (
<button onClick={props.handleClick}>
Click me
</button>
);
}

How do stateless components contribute to the modularity of the code?

Stateless components contribute to the modularity of the code by promoting the use of small, reusable components. Each component can be developed and tested independently, which makes the code more maintainable and easier to understand. It also encourages separation of concerns, where each component is responsible for a single functionality.

Peter BengtssonPeter Bengtsson
View Author

Peter is a full-stack Web Developer at Mozilla where he works in the Web Engineering team. His passion is building fun web apps that are fast as greased lightning and has published open source software for over 15 years. He blogs and experiments on www.peterbe.com.

nilsonjReact-LearnReact.js
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week