Immutability is not a new concept in programming. It serves as the basis for programming paradigms, such as pure functional programming. The whole idea is to avoid direct change to data after it has been created.
Below is a breakdown of what we’re going to discuss in this article:
JavaScript primitives like strings and numbers are known to be immutable. This is true because strings, for instance, can’t be changed by any method or operation. You can only create new strings.
Let’s consider the variable below:
var name = "mark cuban" name = "John Steve"
You could argue that the data is mutable because the name variable was reassigned to another string, but this is not the case.
Reassignment is different from immutability. This is because even though the variable was reassigned, it didn’t change the fact that the string “Eze Sunday” exists. It’s the same reason why adding 3 to 13 wouldn’t change the original variable 13, or you turning 18 doesn’t change the fact that you were 17 before.
Even though variables may be reassigned, the immutable data still remains the same.
We’ve established from the example above that primitives are immutable, but that isn’t the end of the story. There are data structures in JavaScript that are mutable. One of them is arrays.
To demonstrate this, let’s declare a variable and set its value to be an empty array as shown below:
let arrOne = []
We can easily update the content of the above array by using the .push()
function:
arrOne.push(2)
This will add the data, 2, to the end of the array, altering the original array that we previously had.
JavaScript wasn’t written for its data to be exclusively immutable, but there are instances where you need an immutable array or map to easily keep track or keep a record of changes in a data set.
This is evident in the React framework, especially when dealing with states and props. This is where immutability libraries kick in. These libraries help optimize our application, making it easier to track changes in our application. In this article, will be looking at two major immutability libraries. Namely, Immer and Immutable.js.
Immer is one of the many immutability libraries out there that you can use in your application. According to its official website, Immer is based on the copy-on-write mechanism. The whole idea revolves around applying changes to a temporary draftState
, which serves as a proxy to the current state. Immer will let you easily interact with your data while keeping all the benefits that come with immutability.
To use Immer instantly in your application use the following command:
<script src="https://cdn.jsdelivr.net/npm/immer"></script> // It can also be installed in your application using NPM; npm install immer Or with yarn; yarn add immer
With Immer, most immutability works are done with the help of a default function:
produce(currentState, producer: (draftState) => void): nextState
This function takes in the currentState
and draftState
and updates the nextState
to reflect changes made to the draftState
.
Example
Consider the code below:
import produce from "immer" const baseState = [ { todo: "Learn typescript", done: true }, { todo: "Try immer", done: false } ]
New data can be added to the state using the Immer default function, as follows:
const nextState = produce(baseState, draftState => { draftState.push({todo: "Tweet about it"}) draftState[1].done = true })
The baseState
in this case stays untouched, while the nextState
would be updated to reflect changes made to draftState
. You can learn more about Immer from its official website here.
Immutable.js is another option to consider when looking for an immutability library. Immutable.js serves the same purpose as Immer, but it takes a different approach. It provides you with an API for data structures like maps and lists.
Immutable.js can be installed using npm:
npm install immutable
Example
We can perform mapping operations with Immutable.js by requiring map
from the installed package and making use of it, like this:
const { Map } = require('immutable'); const map1 = Map({ a: 1, b: 2, c: 3 }); const map2 = map1.set('b', 50); map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50
From the example above, our object { a: 1, b: 2, c: 3 }
is wrapped with the Map()
function. we went on to perform get
and set
operations on it while keeping the data Immutable.
In addition to objects, we can create immutable arrays using the List
function as shown below:
List(['apple','orange','grape'])
The above is an array implementation in Immutable.js using the List
function.
fromJS function
helps bypass the need for wrapping our objects and arrays with Map({})
and List([])
functions by converting them directly into immutable data.
fromJS(['apple','orange','grape'])
The above converts the array directly into immutable data.
Now here is the big question: which should you choose between these two libraries? To start, let’s list out the benefits and downsides to these libraries individually. We’ll start with Immer.
There are lots of benefits that come with using Immer as opposed to making use of other libraries like Immutable.js. Some of these include:
There are a couple things that make using Immer difficult. For one thing, you need an environment that supports proxy objects to use Immer. Also, Immer does not provide support for complex object types like class instance.
Just like Immer, Immutable.js has its benefits. Some of them includes:
Though Immutable.js is fast at writing data, it is much slower when performing read operations.
Also, Immutable.js forces you to learn new and sometimes complex syntax and APIs just to perform basic operations. For instance, adding an extra data to an array will require the use of a .set()
method, which isn’t traditional for JavaScript.
Immutable.js also forces you to use a unique construction type everywhere in your application. This means that for every immutable collection that you construct, you must make use of the appropriate Immutable.js collection rather than the traditional ones. This can cause a lot of stress, especially when migrating your code to another codebase that doesn’t use these collections.
Immer, on the other hand, offers roughly the same thing with much more flexibility. This is why many developers stick with it. It lets you create collections with classical objects that you’re already used to.
If you need faster writing speed in your application, you can go for Immutable.js. On the other hand, if you want to write less code while sticking with traditional JavaScript data structures and object types, then Immer is for you.
Overall, both Immer and Immutable.js are great libraries that you should try using in your application.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
One Reply to "Immer and Immutable.js: How do they compare?"
It is not correct to say that assignment does not imply mutation. Mutation simply means changing something. Assignment involves a mutation of the environment in which code executes; either a new name is installed which maps to the value corresponding to the right hand side of the assignment expression, or else the value installed under that name is updated to the new value.
Before you say this is pedantic and unimportant, consider that the example given for assignment used the var keyword, which can easily result in updates to the global environment. The effect could be that a function in some other module which could have previously been idempotent in effect loses this property, ie. that now running it before the assignment occurs has a different effect than running it after.
This is analogous to modifying prototypes owned by other modules, such as that of Array or Object; as it may cause undesirable behaviour it should not be done.
The scope of a mutation’s effects may be limited sufficiently by use of strict mode, and for example, the use of const and let instead of var.