React toy project: Tactics (part 3)

In my latest post I finished the game of Tactics, a funny darts game. In the meantime I have been playing it in a pub nearby and found out that the performance of the app is really poor: it is slow, bigtime!

The reason for the poor performance is that in our version we did not take advantage of a thing called ‘shouldComponentUpdate’. This method is called just before a component is rendered and by default always returns true. Why is this a problem?

An example

As an example consider our dartgame with the component hierarchy as outline below. When a player hits a number 17, only that specific <Row /> component should be updated: the number of hits for the number 17 should be incremented and the number should be crossed out. No need for any of the other components to update, right?

  • Tactics [main component]
    • Header
    • TacticsGame
      • PlayerTurner
      • Standings
      • PlayerField
        • Row
          • PointsCounter

Technically however, hitting a 17 means that the state of the application is updated and you know what happens when the state changes: the root component <Tactics /> is rerendered. It renders the <Header /> and the <TacticsGame />, both <PlayerField/> components and so on until finally we arrive at the <Row /> component of the active player and there we see a change in the UI. Needless to say this is a lot of unnecessary rerendering.

Measuring performance

Before we start with the optimization of our code, let’s measure the performance. For this we can use the performance addon from react: react-addons-perf. If we import this module from npm

import Perf from ‘react-addons-perf’

and add it to the window

window.Perf = Perf

we are able to use it in the console. The only methods we need to measure the performance are start(), stop() and printWasted(). So, load the app in your browser and type Perf.start() in the console. Then play around with the app for some time and type Perf.stop() followed by Perf.printWasted(). The latter will show a diagram with the time wasted. Let me just quote this article as it describes what we see perfectly:

It [printWasted()] tells you how much time was wasted doing render tree construction and virtual DOM comparisons that did not result in a DOM modification. The components that are surfaced here are prime candidates to be improved via PureRenderMixin

In our case after clicking for some time throughout the app, the wasted time was something like 170 ms. This seems like a very low number but you definitely feel that the app’s response is quite slow. Let’s look for a solution to this issue.

shouldComponentUpdate to the rescue

Since we know that after hitting the number 17 only that row needs to be rerendered we could write nifty shouldComponentUpdate functions for all components such that they return false (meaning no rerender) in specific situations. Although it is possible, there is an easier way.

PureRenderMixin to the rescue

As the documentation clearly states, as long as your render functions are ‘pure’, the PureRenderMixin can be used for a performance boost. But how does this work?

This mixin will shallow compare the old state with the new state and implements the shouldComponentUpdate function to only update and rerender if they are not equal.

There you go

However, the docs also state that the comparison is a shallow comparison and that for complex, nested objects, there might be false-negatives which means a rerender is not performed while it was supposed to.

Immutability

To overcome this last issue, we need to use immutable data. With immutable data we are sure that the PureRenderMixin will implement shouldComponentUpdate correctly and the needless rerenders of our app are history.

But what is immutable data?

Immutable data is data that can not be changed once created. If we want to change some data in an immutable object we have to create a new object with the changed data. In practice we can use either the react-addons-update module or Immutable.js.

react-addons-update

We start with the react addon, which is a [[small module]] to create a new state from the old one. It comes with an update method which takes the old state object and an object that describe the changes to it. It returns a new version without mutating the old state.

Here is an example of the changeTurn function where you can see it in action:

Note that the setState uses the old state as an optional argument. This is the object from which we create a new one. First we extract the data from it and then we return an object with key ‘data’. The value is the update function from the react-addon which updates the following fields inside data:

  1. gameTurn, we use the $apply command (from the addon, the $ is not jQuery here!) which “passes in the current value to the function and updates it with the new returned value.” In this case the old value was either 1 or 2 and the new value will be 2 or 1 respectively.
  2. player1’s turn property (this is how to update a nested property) for which the boolean value is switched.
  3. idem for player2’s turn property.

Note that we use an ES6 arrow function for the $apply command callback. The ES5 equivalent would have been

gameTurn: {$apply: function(gameTurn){return 3 - gameTurn;}}

We could have used more ES6 functionality and combine the arrow function with destructuring to get the data from the old state in one line like this:

By consistently using this update pattern we never mutate state. This means that using the PureRenderMixin in all our components yields a great performance boost! The PureRenderMixin itself is added to our components like this:

Apart from this addon there is a larger library called Immutable.js which we can also use.

Immutable.js

As the docs describe it: Immutable.js

provides many Persistent Immutable data structures including: List, Stack, Map,OrderedMap, Set, OrderedSet and Record.

You can simply create an immutable object from a normal JS object using the fromJS() method. Similarly, when we need to convert an immutable object to a normal JS object we can use the toJS() method.

Reading data from an Immutable collection is a bit different compared to a normal JS object, because we need to use the get() or getIn() method. This is very powerfull, since retrieving a deeply nested property of an immutable object becomes simple. For example, the number of hits for a specific number for player 1 may be retrieved by

var number = 17;
state.data.getIn([‘player1’, ‘score’, number.toString(), ‘hits’]);

The syntax for creating a new, updated state given the old state is as follows (again for the changeTurn function) using ES6 arrow functions and destructuring:

As we see, the update function takes the property to update and a callback function that returns the new value. Additionally, the updateIn function is like the getIn function discussed before.

Concluding remarks

Using the performance boost available via the PureRenderMixin is critical for the performance of your React app. It implements the shouldComponentUpdate method to ensure that components only rerender after DOM modifications.

To be able to use this mixin, we need to use immutable data and never mutate our state. This can be achieved by either the react-addons-update module or the Immutable.js module.

The code for the Tactics game is available on Bitbucket using either react-addons-update https://bitbucket.org/plintsites/tacticsupdate or Immutable.js https://bitbucket.org/plintsites/tacticsimmutable.


Mijn Twitter profiel Mijn Facebook profiel
Pim Hooghiemstra Webdeveloper and founder of PLint-sites. Loves to build complex webapplications using Vue and Laravel! Latest post
Laravel-mix and the spread operator

Leave a Reply

Your email address will not be published. Required fields are marked *