Should I stay or should I…shouldComponentUpdate()? (I’m trying to make a fun Clash reference here. Unfortunately, Alexa is better at puns than I am).
Let’s say you’ve stepped into a React application to make some updates or been involved in building one yourself. You have been at it a while, and feel somewhat comfortable using React, you understand React’s lifecycle and you may be using React libraries like Redux or MobX to some extent.
I’m assuming you have some familiarity with the React component lifecycle. The React documents very neatly outline the component lifecycle, so there’s no need to go over every step here. A quick read through is well worth the effort.
As with any technology, you start to notice that a few of the pages are looking a little slow. You know performance tuning too early is often a bad idea, but you feel like you might be at that point. After all, in your experience, end users notice even small lags and no one wants that. So what do you do now?
You find yourself doing a quick search to see how to speed up your application. One of the first things you are likely to encounter as a fix is to start using the shouldComponentUpdate()
method. In a standard React component, this method always defaults to true
, but if this method returns false
, the render()
method will not be called again. React render()
calls can be expensive because React creates a new tree of DOM elements to compare against the old tree on each render()
call. It sounds like the perfect solution, to just stop rendering components as often. So, is this the answer you’ve been looking for?
Well, like all things in software development, it might be. I know you thought this would be a quick ‘yes’ or ‘no’ answer and you’d be grabbing an office donut before the food vultures take them all. That’s too bad. Get your donut and settle in for a bit.
In this blog, I introduce six tips for improving performance and design in your React application.
Spoiler alert: there’s a good chance the specific render()
call you are looking at, in and of itself, is not the main source of the issue. We are about to take a dip into redesigning parts your application with some tips for improved performance. The key to solving your problem is to further your understanding of how React works and (re)design your application to use it in the best way possible.
First, let’s talk about the state in React. What defines the state? As you have likely seen, the full state of a component is a combination of some plain JavaScript objects referenced by this.state
and this.props
in the component. The goal is that this.state
represents the local state of the component, while this.props
is passed into the component from a parent. This.props
allows the child to receive updates from the parent. Both can determine what is rendered onto the screen by the component.
Changes to either this.state
or this.props
kick off lifecycle events and render the component again. As far as a child component is concerned, this.props
should be immutable and untouchable. It’s best to think of them as options and to use them for passing in data and callback functions. In contrast, this.state
should be about managing local state.Think
in terms of interactions for this.state
by way of, for example, user input and network response.
The this.state
portion of a component is actually completely optional. You can, and should design components without the local state defined by this.state
when applicable, which brings us to tip #1.
Performance and Design
Tip #1
Consider which components should have no internal state. Extract them out of larger components and let them be what they likely should be; stateless functional components. Think about anything in your application that is purely presentational and unchanging. Looking for components that only define the render()
method is a good place to start.
Stateless components are pure JavaScript functions. Using this option gives you a small, fast, and reusable component. Within reason, the more stateless components you can extract, the better. Using these forces you into a design that won’t allow you to mix state and presentation when it’s not needed. These are sometimes referred to as ‘dumb components,’ but using them can be smart. Start by looking for code that is pure JSX and move that into a stateless, reusable component. After, look for code that just displays unchanging this.props
data and do the same.
An example Stateless component is const Simple = (props) =>
(< div > My Simple Display </ div >);
You have access to the props, but there is no lifecycle, there is no this
available and no local state should be maintained.
Tip #2:
Consider which parts of your components have a simple state that changes infrequently and change them to PureComponents
.
Per the React docs, the litmus test for using a PureComponent
is, if given the same this.props
and this.state
, render()
will always render the same way. Think of a header that just displays a username from a this.props
or a sidebar with some contact information. The data is flat and doesn’t change often.
In terms of whether you should use shouldComponentUpdate()
, asking about PureComponent
is a trick question. A PureComponent
is already changing the shouldComponentUpdate()
for you to do a shallow comparison. If you haven’t thought about a shallow comparison in a while, this means that values like number, strings, and booleans are compared by value and object references are compared based on whether they point to the same object.
Object attributes are not compared and any nested data will be ignored. The comparison operations are cheap. When you use a PureComponent
it forces a design that won’t be drilling down deep into this.props
and this.state
for comparisons.
So when do you want to use PureComponents
? For times when this.state
and this.props
are immutable or don’t have deep objects. If the PureComponent
has children, make sure that they don’t need to update based on the same rules as the parent, because the whole tree will stop re-rendering when false
is returned from the shouldComponentUpdate()
method of the PureComponent
.
Moving on from component types, but still talking about component state, let’s follow with some more areas to look into in your code.
Tip #3:
Keep this.state
and this.props
separate in your design. Sometimes a developer will set the this.state
of a component to a this.props
value passed in on initialization. This is contraindicated by the React documents in most cases, which say that the only time to do this is if you need a seed value for your this.state
.
That said, in real life, you will probably see these things happen. The problem here is that, in a regular component, any changes to this.props
will kick off another render()
. In addition, you will now be forced to manage code to keep up with this.props
updates in your local this.state
and updates to this.state
also kick off another render()
call. Not even going into the evils of multiple sources of truth, you are now double rendering in every instance. Avoid setting duplications of parent states (this.props
) into the local this.state
whenever possible (which is almost always).
construction(props){ super(props); This.state = { value1: props.value1;// avoid this } }
Let’s follow up with some more thoughts about this.state
.
Tip #4:
Keep a close eye on how this.state
is being used.
You should only use this.state
for types of interaction. Think of this.state
as something you want to minimize using as much as possible. Break code down to small components with limited, self-managing this.state
scopes. Set up children components to be self-maintaining members of your application and make children responsible for updating themselves.
For example, do you have a list of items to pass in via this.props
? Each child should only be aware of the one element of the list it owns, not the entire list.
Try to limit where data is retrieved and updated within the scope of the smaller components. For example, if you are connecting via Redux, split up your connected components. Then, when possible, manage the one connection the single component is interested in via the connection in that component. When you must pass down data, make copies of your objects for local, isolated usage. Avoid connecting to one very large object in a parent and passing that through a large number of children.
When you start thinking about what a component should own locally, your design will improve and subsequently so will your performance. But you still want to keep render()
from being called again? Well, let’s talk about render()
a little bit, too.
Tip #5 :
Clean up the render()
method.
Until now we have been talking about preventing render()
from being called again. But what about making render more efficient? Yes, this is another place you can look for performance drags.
For one thing, it’s not uncommon to see a component binding a function to a child inline where the component is created:
<ChildComponent methodA = {this.methodA.bind(this)}/>
Certainly, this looks straightforward and prevents you from forgetting to bind in the constructor. Unfortunately, this also causes a completely new method to be created on every render()
, which may not be obvious at first glance.
This also applies for arrow functions.
An extra render()
call for a component might not be a big deal but reallocating a method from a parent to say, multiple children in a list can get expensive because now changes that might be ignored by children will always call render()
again. To prevent this side effect, define your methods outside of render()
and move your method bindings to the constructor.
If you need to transform data before you display it, don’t do so in render()
. Especially when those transformations mean creating new references. Where you do data transformations may differ based on the libraries you are using, but avoid new references to objects in the render()
method itself.
Bonus Tip:
If you still are seeing issues, before you do anything else you may want to look into the Profiling Components with the Chrome Performance tab section of the React docs. They show you step by step how to view when and why your UI may be updating. Plus, performance tools are fun! Okay, that may just be me, but the information is still very useful.
We’ve been talking about this forever…should I use shouldComponentUpdate()
or not?!
Once your design has been squared away, you will probably find that there are limited usages for using this method to determine the need for a render()
call. If you do use this method, then you need to make sure you are avoiding complicated deep compares or you may actually be slowing things down instead of speeding them up. And if you are doing a shallow compare, there’s a good chance you could already be using a PureComponent
. If you are digging deep into a data structure, stop immediately and accept that the design likely needs a change.
With performance, there is always more to learn and the whole topic has books of information, but my belief is that understanding how things work is a big step down the path. I hope this gets you started.