Native vs ImmutableJS vs Immer — Are libraries the way to go for immutability in React?

Maintaining immutability when dealing with objects and arrays is very important in React. It ensures that the DOM updates correctly and predictably. But when we have nested data structures, maintaining immutability in our data can get ugly, very fast. This article explores 3 approaches to immutability — doing it natively, using ImmutableJS (a library which internally uses TRIE structures and hash maps) and using Immer (a library which internally uses copy-on-write by maintaining a draft state).

Note: If you’re already aware about how these libraries work, skip ahead to the end of the article to see a detailed performance and usability comparison. (The results may surprise you!)

As an example, throughout this article, we’ll use a data store for posts — each one with its own comments and compare how these three approaches measure up against one another.

This is what the JSON would look like. Now, when we update posts, we may be doing it at different levels.

  1. At the most shallow level, we might add a post to the main array.
  2. One level deeper, we might change a property of the post — say the title.
  3. Two levels deeper, we might add a comment to the comments array.
  4. At the deepest level, we might change the content of a comment.

Native Javascript

In native Javascript, maintaining immutability means that we need to create a copy of the data every time we update a property. This is very straightforward at a shallow level using the spread operator — but it can get very messy as we go deeper.

You can see that updating a comment can be really convoluted and long. Let’s explore the other approaches.

Immutable.JS

Immutable.JS is a library that converts plain Javascript objects and arrays into Lists and Maps. Lists and Maps can be TRIE structures or hash maps that are quicker to traverse.

When you’re using Immutable.JS, it will always return a copy when you update the data and share as much of the old structure as possible. The same actions that we performed above become much shorter with Immutable.JS. However, these aren’t regular Javascript objects and need to be converted back and forth to work with APIs and 3rd party libraries.

Immer

Immer uses a produce function to internally create a draft state. You can mutate this draft state as you would normally mutate a regular Javascript object or array. At the end of your mutation, Immer will compare the draft state with the original state and return a copy only if there are any changes (copy on write)

The syntax uses regular Javascript, which makes it very easy to learn and implement. There’s just a wrapper function around your data updates. This pattern is extremely easy to use with Redux. Also, since it returns regular Javascript objects, it’s easy to use with APIs and 3rd party libraries. One thing I love about Immer is that it is totally opt-in. You could use it in very few parts of your app where you need deep updates and nowhere else!

Performance

When I started looking into immutability and libraries, I heard a lot about how much more performant Immutable.JS was, so I ran the numbers for myself. And I found that to be true — but only partially. Immutable.JS is extremely performant as compared to native operations — but only when you’re dealing with shallow data!

You can verify this for yourself — run these operations in a loop and measure them using a console.time. I’d love to know in the comments if you find something different.

In almost all cases, native Javascript outperformed both Immutable.JS and Immer. The only case where Immutable.JS faster was writing to flat arrays. Immer was slower in all cases, and that is to be expected. In my opinion, the advantage of Immer lies in that it’s completely opt-in. For things like mapping, reducing, filtering — where the method returns a new array in any case, you can simply by-pass it and perform it natively.

Let’s talk about Immutable.JS and why I wouldn’t recommend using it in applications:

1. Immutable.JS is faster for flat data structures — but really how are you dealing with completely flat data in real world applications? All the data we work with, are, at the least arrays of objects.

2. One important thing that you can notice is that creating an Immutable.JS List or Map from a native object or array is very very expensive. Now, in real world applications, most of the times that we update data is when we also update it on the backend through an API request. Everytime we recieve the data from the backend, we need to convert native object into an immutable Map and vice versa while sending data over an API. This is significantly more expensive — even when you compare it to using Immer.

Ease of Development

1. In terms of ease of use, Immutable.JS does have it’s own syntax which isn’t very difficult to use. Immer uses the same syntax as native Javascript — which works really well. Natively maintaining immutability can become convoluted very quickly as we saw.

2. In terms of lines of code, both Immer and Immutable.JS considerably reduce your effort.

3. You can’t send Immutable.JS Lists or Maps over APIs or when using most 3rd party libraries.

4. As we saw earlier, Immer is completely opt in. Immutable on the other hand tends to spread into your entire code base once you start using it.

Conclusion

Although the performance of Immutable libraries isn’t as good as native — it is unlikely that these operations are the bottlenecks in your application. So, if you need to use a library, or are already using one in a project — I wouldn’t recommend that you take efforts to rework your entire app. However, when you’re starting a new project, I think the best way to move forward would be to use a library that uses native Javascript operations to maintain immutability, but offers a better syntax. Immutability-helper is one such library.

If you’re already using Immer, try to use it with discretion — only where you have nested data.

Immutable.JS could potentially have many benefits — but, I wouldn’t recommend using it for now.

One thought on “Native vs ImmutableJS vs Immer — Are libraries the way to go for immutability in React?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.