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.
- At the most shallow level, we might add a post to the main array.
- One level deeper, we might change a property of the post — say the title.
- Two levels deeper, we might add a comment to the comments array.
- At the deepest level, we might change the content of a comment.
You can see that updating a comment can be really convoluted and long. Let’s explore the other approaches.
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.
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
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.
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.