So you heard React was fast, and decided to give it a go. You grabbed a boilerplate, started working through it, and noticed shouldComponentUpdate
and PureRenderMixin
. Some googling reveals these are the things you use to make React fast. But wasn’t React already fast?
The answer is that React is fast… sometimes. The other times, you should use shouldComponentUpdate
. But when are the other times? Wouldn’t it be so much easier if there was just a simple yes-no question which you could ask to decide whether to use shouldComponentUpdate
or not? And wouldn’t you believe it, there is!
Will shouldComponentUpdate
provide a measurable and perceivable performance improvement?
If not, don’t use it.
So you mean I probably shouldn’t be using it?
You probably should avoid it. The React team called shouldComponentUpdate
a performance escape hatch for a reason — and you’d better bloody well hope that an escape hatch isn’t meant for regular use. Of course, “probably not” doesn’t mean “definitely not”.
React wouldn’t have a performance escape hatch if it wasn’t sometimes useful. The trick is that you need to ask yourself if you should use it — not whether you shouldn’t. And if you’re not a special snowflake, well, you’ll probably use it less often than not.
But hang on a moment, you say. My circumstances are different. And… you’re probably right. So let’s have a look at the reasoning behind the rule, starting with:
Adding shouldComponentUpdate
will often slow your component down
Yes, you read that correctly. A performance escape hatch can actually slow your component down. But how? To answer this question, we need to zoom out for a moment to a sky-high level, and ask: what is React?
React is basically a very clever implementation of shouldComponentUpdate
.
But James, you say. React does a lot more than just figure out whether to update a component. And you’re right. As well as deciding if a component should update, React also decides how to do it. But you can’t have one without the other, and it turns out React is generally pretty good at both.
Ok, so React is good at deciding whether a component should update. How does it work this magic?
Great question! So you know those render
methods which you write for each of your components? It may look like they’re returning funny HTML-in-JavaScript (or ReactElement
objects if you use Raw React). But you may be surprised to hear that render
actually returns plain old JavaScript objects. They look something like this:
{
type: 'ul',
props: { className: 'what-do-you-want-to-do-tonight' },
children: [
{ type: 'li', children: 'The same thing we do every night, pinky.' },
]
}
Sure, it might be all dressed up in JSX or calls to React.createElement
, but the true form of render()
‘s output is just an object representing your markup. And if that object hasn’t changed from the previous render, there is obviously no need to update the DOM.
Or to put it another way, React basically implements shouldComponentUpdate
for you for free. If we pretend render
receives its props as an argument instead of attached to this
, React’s implementation would look something like this:
shouldComponentUpdate(nextProps) {
return !deepEquals(render(this.props), render(nextProps))
}
As you know, deepEquals
will be fast on small objects, and slow on heavily nested ones. So this approximation gives us a good rule of thumb:
If the value returned by render
is tiny but props
is heavy, shouldComponentUpdate
will likely do more harm than good.
But say you have a case where the value returned by render()
is large enough that you can gain something by writing shouldComponentUpdate
yourself?
shouldComponentUpdate
gains are often insignificant
So let’s say you’re rendering something heavy like a HTML table. You’re pulling your data from your props
, but you’re also doing some computations on it within your render
method. And to make this the perfect scenario, your data uses Immutable.js such that changes in props
can be found with simple reference equality.
In this scenario, shouldComponentUpdate
will provide a huge speed up over React’s default behaviour. In fact, my experience has been that a 10 times speedup is not unusual at all. But if this is the case, how can the gains be insignificant? The answer is of course:
A 10x speedup of less than 1 millisecond is still less than 1 millisecond.
Donald Knuth wrote that “premature optimization is the root of all evil” years before JavaScript was even imagined, but that does not make it any less relevant today.
Of course, us JavaScript programmers are not known for learning things the easy way, and a number of you are probably now asking “but some things are less evil than others, right?” After all, how much can a short three-line function really hurt when it isn’t even going to slow things down? Actually, my experience is that it can be downright painful.
Maintaining shouldComponentUpdate
is hard
So if I had to guess, the reason the React team called shouldComponentUpdate
an “escape hatch” instead of a “turbo button” is that maintenance is hard. But honestly, I think an “escape hatch” is the wrong analogy. shouldComponentUpdate
is actually more like unprotected sex.
The thing is, shouldComponentUpdate
is sometimes necessary, and certainly makes your app respond better. But it also is a major cause of bugs — and not ones which appear straight away.
But seriously, maintaining code with shouldComponentUpdate
is hard. Add a new prop to render
but forget to update shouldComponentUpdate
? There’s a bug. Pass in a factory function as a prop
which uses state from somewhere innocent like the current route? Another bug. And the worst thing is that these bugs often don’t appear in tests, because they don’t break until something, somewhere changes. And probably when you first demonstrate it to your customer at that.
But if shouldComponentUpdate
truly causes this many bugs, it obviously must still be used on a somewhat regular basis. So the question becomes…
When should I use shouldComponentUpdate
?
At the risk of repeating myself, this question has a very simple answer:
Use shouldComponentUpdate once you’ve measured that it provides a perceivable performance improvement.
Hopefully the previous section has convinced you that shouldComponentUpdate
has a number of downsides. With these in mind, the trick is to only use it when you’re sure the upsides beat them. And to do that, you’ll need to use a ruler. A really fancy technological one.
Measuring performance improvements
While there are a number of ways you could try to measure the improvement given by shouldComponentUpdate
, this article is going to focus on doing so with a JavaScript Profiler.
But what is a profiler? It’s that fancy ruler I mentioned. A good profiler will let you measure basically anything about your program. But in particular, it allows you to break down your script’s total running time by your functions:
By comparing the time spent in render
both before and after we add shouldComponentUpdate
, we can put a number to any performance improvement. Of course, we’ll also need to take into account time spent in shouldComponentUpdate
— it isn’t free. Finally, with some mental gymnastics we can decide if the extra maintenance and general uncouthness of shouldComponentUpdate
is worth the speedup.
Easy as 1-2-3, right? But don’t take my word for it – let’s walk through an exercise which demonstrates it. Right after giving you the opportunity to receive a free high-resolution React cheatsheet by subscribing to my newsletter:
Get the PDF cheatsheet!
Exercise
To demonstrate, I’ve put together an example fiddle. The Enterprise
component inside it is designed to meet the requirements which make shouldComponentUpdate
a serious consideration; it does processing on the original value, it frequently re-renders, and its props
contain immutable data.
To complete the exercise, all you need to do is create a copy of the project and follow the steps below.
Before getting started, make sure you know how to start your browser’s profiler. You can find instructions at the Chrome or Firefox websites.
Step 1: Measure the standard version
Let’s start by measuring the time spent in render
when the user clicks an action:
- Open the JavaScript profiler for the example fiddle’s window
- Start recording
- Click the “Toggle synergy!” link and let the page cycle for ~5 seconds
- Stop recording by click the same button you used to start
- Find the most costly
render
method by sorting by “self” time — it should be near the top of the list. Write down the duration of time spent in it. For this you want is the”total” time, i.e. time spent in both therender
function itself, as well as functions it calls.
But what if the times spent in render
are small enough that you can’t find it near the top of the function list? Great! You don’t need shouldComponentUpdate
. Or at least not until you fix all your other performance issues.
Step 2: Measure the shouldComponentUpdate
version
The next step is to measure the amount of time spent rendering once we add shouldComponentUpdate
to the App
component. Obviously, to do so you’ll need a shouldComponentUpdate
. Here’s one I prepared earlier:
shouldComponentUpdate(nextProps, nextState) {
return !Immutable.is(this.state.synergy, nextState.synergy)
},
Once you’ve added in shouldComponentUpdate
, repeat the process in step 1. And once you’re done, finish off your results by finding and adding the time spent in shouldComponentUpdate
to the time spent in render
. Here are my results:
render
with shouldComponentUpdate
:
shouldComponentUpdate
:
And there you have it! You now roughly know what kind of gain your component gets from shouldComponentUpdate
. That is, if you get a gain at all.
But did you notice how I emphasised roughly? Make sure to keep in mind that this method is not all that precise – the actual results can vary a lot. If you don’t believe me, just try repeating the above and seeing how much they differ. But if the results are so inaccurate, how are you to ever make a good decision?
Making a decision
At the risk of sounding abnormally shitsukoi…
You only want to use shouldComponentUpdate
if your measurements show you beyond any reasonable doubt that it makes sense.
What is beyond reasonable doubt? My experience has been that if adding shouldComponentUpdate
halves your running time, it is probably actually providing a benefit — and not occurring by chance.
But with this said, keep in mind that the benefits you receive can only be as great as the original running time is long. If your original render
function only takes on the order of 100ms, it is a tossup as to whether any benefits will be perceived at all.
At the end of the day, the decision has to be made in the context of the project you’re writing. But by keeping in mind the maintenance downsides, the measurement uncertainties and the ranges in which benefits can actually be perceived, you’ll be equipped to make the right decision. Of course, this all assumes that you’re measuring a well implemented shouldComponentUpdate
in the first place.
Writing an effective shouldComponentUpdate
So you’ve found the perfect spot for a dirty little shouldComponentUpdate
— but how do you write it?
Now I could just wave my arms and then proclaim “Immutable.js!” But then I imagine you’d feel a little like this:
The thing is, Immutable.js isn’t so much the solution as it is a (mindblowingly awesome) tool to build your own solution. And it isn’t the only tool either; plain ol’ Object.assign
will get you most of the way there.
Ok. But if immutable state won’t solve your problems, what will?
I’m glad you asked! The real solution is well structured state. In fact, well structure state will solve more than just your shouldComponentUpdate
implementation; it will fix all of your problems down to the Jehova’s Witnesses waking you up to preach to you even after moving to Japan. It is really that good.
But how do you structure your state well? That’s a great question, and is one I’m going to be answering in a couple posts time — right after my (nearly finished) Luddite’s guide to Redux State. Don’t want to miss it? Then subscribe! You’ve got nothing to lose. Actually, you stand to gain the high resolution PDFs of these five React and JS cheatsheets:
I will send you useful articles, cheatsheets and code.
One more thing – I love hearing your questions, offers and opinions. If you have something to say, leave a comment or send me an e-mail at james@jamesknelson.com. I’m looking forward to hearing from you!
Just wanted to say how much I appreciate your articles. They are consistently well-written, clear, relevant, and informative.
I’m at the point where I’m not learning anything new from them myself, but they serve as great tutorials for anyone still in the earlier stages of learning the React ecosystem.
I keep a list of high-quality React and Redux articles over at https://github.com/markerikson/react-redux-links. I’ve got a couple of your articles in that list already, and this one is absolutely going in the “Performance” section.
Thanks!