React: A (very brief) talk about immutability.

Cássio Zen
Pro React
Published in
5 min readAug 24, 2015

React component’s API provides a setState method to make changes to the component internal state — but as the documentation makes clear, we have to be careful to always use the setState method and never manipulate this.state directly.

As a rule of thumb, treat this.state as if it were immutable. For example, let’s say we have this given stateful component that displays data about a voucher for an airline travel (the render method is omitted in this example because we are only investigating the component’s state):

Now, suppose we want to add a passenger to the passengers Array. If we’re not careful we may unintentionally mutate the component state directly. For example:

The problem in this sample code, as you may have guessed, is that in Javascript objects and arrays are passed as reference. This means that when we say updatedPassengers=this.state.passengers we’re not making a copy of the array — we are just creating a new reference to the same array that is in the current component’s state. Further on, by using the array method push, we end up mutating its state directly.

By manipulating this.state directly you are circumventing React’s state management, which can be potentially dangerous as calling setState() afterwards may replace the mutation you made.

The alternative is actually pretty simple: create copies of the objects in this.state and manipulate the copies, assigning them back using setState(). It’s possible to use non-destructive methods, that is, methods that will return a new object or array with the desired mutations instead of actually changing the original object or array.

Map, filter and concat are just a few examples non-destructive array methods. Let’s re-approach the earlier problem of adding a new passenger to the array, this time using the Array’s concat method:

The same rule applies for mutating objects: the correct approach is to mutate a copy of the object in this.state and then assigning the new object with setState. There are different ways of doing this, like using Object.assign. For example, to change the flightNo on the ticket state key we could do:

Nested Objects

Although Array’s non destructive methods and Object.assign will do the job on most cases, it get’s really tricky if your state contains nested objects or arrays. That’s because of a characteristic of the Javascript language: Objects and Arrays are passed by reference, and neither the Array’s non-destructive methods nor Object.assign makes deep copies. In practice this means the the nested objects and arrays in your newly returned object will be only references to the same objects and arrays on the old object.

Let’s see this in practice — given the ticket object we were working on:

If we create a newTicket object with Object.assign, like in this example:

We will end up with these two objects:

However, the departure and arrival objects on newTicket aren’t separate copies — they’re references to the same originalTicket objects. If we try to change the arrival object on newTicket, for example:

Both objects new share the same arrival airport…

Again, this has nothing to do with React, its just the default Javascript behavior — but this default behavior can and will impact React if you want to mutate a component State with nested objects. We could try making a deep clone of the original object, but this wouldn’t be a good option because it is expensive on performance and even impossible to do in some cases. The good news is that there is a simple solution: React provides a set of utility functions (called ‘immutability helpers’) that help update more complex and nested models.

React Immutability Helpers

React Immutability helpers works on regular JavaScript Objects and Arrays and it’s easy to get started and integrate on a React App.

To begin with, there’s no need to install any additional library- just change the import on you javascript from:

import React, { Component } from ‘react’;

to:

import React, { Component } from ‘react/addons’;

React.addons.update accepts two parameters: The first one is the object or array that you want to mutate. The second parameter is an object that describes WHERE the mutation should take place and WHAT kind of mutation you want to make. Let’s exemplify:

Given this simple object:

To create a new shallow copy of this object with a new grade, the syntax for React.addon.update is:

The object {grades:{$push: [‘A’]}} informs, from left to right, that the update function should:

1- Locate the key grades (“where” the mutation will take place)

2- Push a new value to the array (“what” kind of mutation should happen)

If we wanted to completely change the array, we could use the command $set instead of $push:

There’s no limit to the amount of nesting you can do. Let’s head back to our voucher ticket object, where we were having trouble creating a new object with a different arrival information. The original object was:

The information we want to change (airport) is nested three levels deep. In React.addons.update, all we need to do is keep nesting objects with their names on the objects that describe the mutation:

Now only the newTicket has the arrival airport set to “MCO”. The original ticket mantains the original arrival airport.

Array indexes

It’s also possible to use array indexes to find WHERE a mutation should happen. For example, if we wanted to mutate the first codeshare object (the array element at index 0):

Besides $push and $set, the immutability helpers also provides $unshift, $splice and $concat (similar to JavaScript homonymous array prototype functions) besides $merge and $apply. The documentation page offers a detailed description of each.

--

--