Immutable React

Learn how to enhance your application with immutability to increase the performance and avoid mutation bugs!

As we discovered in Chapter 4: Redux, immutability is quite helpful when developing applications! It makes it so much easier to reason about what is happening to your data, as nothing can be mutated from somewhere entirely different.

The problem is that JavaScript is by default a mutable language. Other developers that don’t have this intricate knowledge of immutability might still mess up, mutate the state and break our app in unexpected ways.

Facebook released a second library called Immutable.js that adds immutable data structures to JavaScript! Let’s see what this looks like.

Introduction to ImmutableJS

If you want to follow along with the initial explanations, you’ll have to npm install immutable!

ImmutableJS exports this nice little fromJS function that allows us to create immutable data structures from your standard JavaScript objects and arrays. (it also adds a toJS method to make objects and arrays out of them again) Let’s create an immutable object:

If you now tried to do object.some = 'notobject', i.e. tried to change the data inside this object, immutable would throw an error! That’s the power of immutable data structures, you know exactly what they are.

Now you might be thinking “But then how can we set a property?”. Well, ImmutableJS still let’s us set properties with the set and setIn methods! Let’s take a look at an example:

If you now console.log(immutableObject.toJS()) though, you’ll get our initial object again. Why?

Well, since immutableObject is immutable, what happens when you immutableObject.set is that a new immutable object is returned with the changes. No mutation happening, this is kind of like what we did with Object.assign for our reducers!

Isn’t that nice? Now, here’s the last trickery in our reducer, because what do we do for SET_SELECTED_TEMP and SET_SELECTED_DATE? How do we set state.selected.temp?

It turns out Immutable provides us with a really nice function for that called setIn. We can use setIn to set a nested property by passing in an array of keys we want to iterate through! Let’s take a look at that for our SET_SELECTED_DATE.

If you now try to run your app though, nothing will work and you’ll get an error.

This is because in our App component we have a mapStateToProps function that simply returns the entire state! An easy trick would be to return state.toJS, kind of like this:

function mapStateToProps(state) {
return state.toJS();
}

In fact, try this and you’ll see that works! There’s two downsides to this approach though:

Converting from (fromJS) and to (toJS) JavaScript objects to immutable data structures is very performance expensive and slow. This is fine for the initialState because we only ever convert that once, but doing that on every render will have an impact on your app.

You thus lose the main benefit of ImmutableJS, which is performance!

Now you might be thinking “But if it’s so expensive, how can ImmutableJS have performance as its main benefit?”. To explain that we have to quickly go over how ImmutableJS works.

How ImmutableJS works

Immutable data structures can’t be changed. So when we convert a regular JavaScript object with fromJS what ImmutableJS does is loop over every single property and value in the object (including nested objects and arrays) and transfers it to a new, immutable one. (the same thing applies in the other direction for toJS)

The problem with standard JavaScript objects is that they have reference equality. That means even when two objects have the same content, they’re not the same:

In the above example, even though object1 and object2 have the exact same contents, they aren’t the exact same object and thus aren’t equal. To properly check if two variables contain the same thing in JavaScript we’d have to loop over every property and value in those variables (including nested things) and check it against the other object.

That’s very, very slow.

Since immutable objects can’t ever be changed again, ImmutableJS can compute a hash based on the contents of the object and store that in a private field. Since this hash is based on the contents, when Immutable then compares two objects it only has to compare two hashes, i.e. two strings! That’s a lot faster than looping over every property and value and comparing those!

Now try using the app for a bit, clicking around, request data for different cities. What you might notice is that the Plot rerenders even if we only change the location field and the plot itself stays the exact same!

This is a react feature, react rerenders your entire app whenever something changes. This doesn’t necessarily have a massive performance impact on our current application, but it’ll definitely bite you in a production application! So, what can we do against that?

shouldComponentUpdate

React provides us with a nice lifecycle method called shouldComponentUpdate which allows us to regulate when our components should rerender. As an example, try putting this into your Plot:

Now try loading some data and rendering a plot. What you see is that the plot never renders. This is because we’re basically telling react above that no matter what data comes into our component, it should never render the Plot! On the other hand, if we return true from there we’d have the default behaviour back, i.e. rerender whenever new data comes in.

As I’ve hinted with the variable above, shouldComponentUpdate gets passed nextProps. This means, in theory, we could check if the props of the Plot have changed and only rerender if that happens, right? Something like this:

Well, here we hit the problem we talked about above. ({ twitter: '@mxstbr' } !== { twitter: '@mxstbr' }) Those will always be different since they might have the same content, but they won’t be the same object!

This is where ImmutableJS comes in, because while we could do a deep comparison of those two objects, it’s a lot cheaper if we could just do this:

In our mapStateToProps function, instead of returning state.toJS() we should just return the immutable state. The problem is that redux expects the value we return from mapStateToProps to be a standard javascript object, and it’ll throw an error if we just do return state; and nothing will work.

So let’s return an object from mapStateToProps that has a redux field instead:

function mapStateToProps(state) {
return {
redux: state
};
}

Then, in our App we now have access to this.props.redux! We can access properties in there with this.props.redux.get (and getIn), so let’s replace all instances where we access the state with that.

Let’s start from the top, in fetchData. There’s only a single this.props.location in there, which we replace with this.props.redux.get('location'):

Now, for the next one (this.props.data.list[0].main.temp) you might think of writing this.props.redux.getIn(['data', 'list'])[0].main.temp, but the problem is that this.props.redux.getIn(['data', 'list']) is an immutable array too!

We still haven’t solved the original problem though, the Plot still rerenders everytime something changes, even if it’s not related to the Plot. Really, the only time we ever want that component to rerender is when either xData or yData changes!

Let’s apply our knowledge of ImmutableJS and of shouldComponentUpdate, and fix this together. Let’s check if this.props.xData and this.props.yData are the same and only rerender if one of them changed:

Additional Material

Author

Max is the creator of react-boilerplate, one of the most popular react starter kits, the co-creator of Carte Blanche and he co-organises the React.js Vienna Meetup. He works as an Open Source Developer at Thinkmill, where he takes care of KeystoneJS.