This blog is going to be fun. I moved from JS to Java early in my development career.
I never got time to understand the JS ecosystem deeply. Later, I moved from Java to JS, and most things did not make sense to me in terms of the object-oriented and class-based to a more functional approach to building things.
In this process, I stumbled upon a code with the heavy use of immer
!
That piqued my interest in this library and why we would need such a thing in JS. Ok, wait, hold on, take a deep breath. Lets learn something very, very interesting about JS
Let's assume we have this Object
const user = {
username: "divyanshu",
dob: "13-10-1990",
phone: "+919998887767"
}
const updatedUser = user
updatedUser.username = "divyanshu2"
console.log(user, updatedUser)
When we print this
{ username: 'divyanshu2', dob: '13-10-1990', phone: '+919998887767' }
{ username: 'divyanshu2', dob: '13-10-1990', phone: '+919998887767' }
[Finished in 141ms]
As you can see, both Object printed, but the value is the same as modified, which means when we assigned user
to updatedUser
it did not copy the values but copied the reference to that Object to updatedUser
.
In simple terms, if in memory user
has reference address as 0x001
where this Object is stored, and we later did updatedUser = user
, this means we assigned 0x001
to updatedUser
, and not the whole Object was copied to updatedUser
, now any change made to updatedUser
would reflect in user object as well because both of them share the same memory, and logically we are talking about the same Object with two different names.
Ok, Now the problem with this is, in our application, how can we copy the data from 1 object to another, so this kind of change on the original Object can be stopped?
This is also known as immutability.
In object-oriented and functional programming, an immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object, which can be modified after it is created
It is a good practice in development. You want all your Object to be immutable unless there is a strong reason not to do it.
This keeps side-effects to a limit. One change somewhere in the application does not affect another part of your application.
Ok, so how can we copy an object with confidence?
const user = {
username: "divyanshu",
dob: "13-10-1990",
phone: "+919998887767"
}
const updatedUser = {...user} // Added a spread operator
updatedUser.username = "divyanshu2"
console.log(user, updatedUser)
With ES6, we have a new spread operator denoted with ...
. This does not pass the reference but copies the content from the Object to the assigned Object.
Here is the output with this change
{ username: 'divyanshu', dob: '13-10-1990', phone: '+919998887767' }
{ username: 'divyanshu2', dob: '13-10-1990', phone: '+919998887767' }
[Finished in 56ms]
As you can see now, the value of username
is different on both Objects, change in updatedUser
does not affect the old user
object. Perfect! Problem solved.
Let's go home and build the next billion-dollar application now.
wait...
This is JS. It cannot be so simple in this world.
Ok, so let us try another little complex Object.
We have now just added a new object as profile
in our user
object, which is an object containing image
and a verified
boolean.
And in the copied updatedUser
, we change the verified
to true
.
const user = {
username: "divyanshu",
dob: "13-10-1990",
phone: "+919998887767",
profile: {
image: "profile.png",
verified: false
}
}
const updatedUser = {...user} // Added a spread operator
updatedUser.username = "divyanshu2"
updatedUser.profile.verified = true
console.log(user, updatedUser)
Print this now.
{
username: 'divyanshu',
dob: '13-10-1990',
phone: '+919998887767',
profile: { image: 'profile.png', verified: true }
} {
username: 'divyanshu2',
dob: '13-10-1990',
phone: '+919998887767',
profile: { image: 'profile.png', verified: true }
}
[Finished in 51ms]
Hmm, now you see the spread worked on username
, profile
again changed for old Object with change in new updatedUser
Object.
Why is that?
Because in JS, every Object, when created, is stored in a memory (Obviously), but when copied, it only passes the reference.
So we only spread the user
object, and never told anything about profile
Object. Hence when it was copied, it again passed its reference (duh!).
how to solve this, well quick and not so easy solution,
const user = {
username: "divyanshu",
dob: "13-10-1990",
phone: "+919998887767",
profile: {
image: "profile.png",
verified: false
}
}
const updatedUser = {...user, profile : {...user.profile}} // Added a spread operator on profile too
updatedUser.username = "divyanshu2"
updatedUser.profile.verified = true
console.log(user, updatedUser)
This would work now, as we added this spread on profile
as well.
const updatedUser = {...user, profile : {...user.profile}}
The problem here though, is, for a straightforward object, we would know the keys, which are Object, but what if the Object becomes more complex and the data might be coming from the backend, which we don't even know about unless we parse it.
This can become a huge problem to solve.
The way we copied with spread operator where nested Object not copied but passed as reference is called shallow cloning, as we never cloned the complete Object.
Deep cloning is a method when we are 100% sure that the newly copied Object does not have any reference to any other object in our code.
How to do it in super easy way.
const user = {
username: "divyanshu",
dob: "13-10-1990",
phone: "+919998887767",
profile: {
image: "profile.png",
verified: false
}
}
const updatedUser = JSON.parse(JSON.stringify(user))
updatedUser.username = "divyanshu2"
updatedUser.profile.verified = true
console.log(user, updatedUser);
Yup, the most Stupidly easy way.
you stringify
the Object, which means convert it to a string, and later parse
the string to convert it to a JSON object.
This will guarantee that whatever is copied is copied 100% deep clone.
I would never prefer this way of cloning the data.
Problem 1
Some of the data can be lost with this approach, it works pretty good with the above example, but if our keys contain values like undefined
, function
, all of those would be converted to {}
.
Problem 2
Doing this stringify
and parse
is the most inefficient way. It takes a lot of time to convert Object to string and later to an object. Compared to other methods of deep cloning, this way is the slowest.
We have other manual ways and libraries which can help with deep cloning.
But apart from those, to work in an immutable way, where you never make changes to the original Object, one of my favourites is immer
Immer isΒ a small library created to help developers with immutable states based on a copy-on-write mechanism, a technique used to implement a copy operation on modifiable resources
This is the definition of this library. To me, as a noob in JS back in 2016, this made no sense. The main reason for not being able to understand how everything in JS is actually an Object, most of the time we don't create Object in JS but rather work with functions (which themselves are first-class Objects)
As per our previous example, how can we modify the values without changing the original Object by using immer
?
const user = {
username: "divyanshu",
dob: "13-10-1990",
phone: "+919998887767",
profile: {
image: "profile.png",
verified: false
}
}
const updatedUser = immer.produce(user, draft => {
draft.profile.verified = true
})
console.log(user, updatedUser);
with immer
installed in your dependencies, you can simply use immer.produce
produce takes 2 arguments, 1 is the Object which we want to copy and modify, and another is a function which will have draft
param.
Here draft
is the deeply cloned copy of the first passed Object, and in the function, you can make the changes to the draft.
All the changes made to the draft will be immutable, and the original Object remains unaffected.
This is the simplest and fastest way to achieve immutability in the code.
I hope this will be helpful in understanding something new in JS.
π Thanks for reading.
X
I'd appreciate your feedback so I can make my blog posts more helpful. Did this post help you learn something or fix an issue you were having?
Yes
No
X
If you'd like to support this blog by buying me a coffee I'd really appreciate it!
X
Subscribe to my newsletter
Join 107+ other developers and get free, weekly updates and code insights directly to your inbox.
Email Address
Powered by Buttondown
Divyanshu Negi is a VP of Engineering at Zaapi Pte.
X