Wouldn't it be nice if copying objects were as simple as reassigning them to new objects?
const object = {
One_A: true,
One_B: true,
One_C: {
Two_A: true,
Two_B: {
Three_A: true,
},
},
}
const newObject = object
newObject.One_C.Two_B.Three_A = false
console.log("newObject: ", newObject)
console.log("object: ", object)
Assigning object
to newObject
will give us these results in the console:
OUTPUT:
newObject: {
One_A: true,
One_B: true,
One_C: { Two_A: true, Two_B: { Three_A: false } }
}
object: {
One_A: true,
One_B: true,
One_C: { Two_A: true, Two_B: { Three_A: false } }
}
Changing a value at any level in newObject
changed the value in object
. Why? Objects in Javascript are passed by reference, not value. The top-level object and each nested object of newObject
share the exact same locations in memory to those of object
. Passing by reference means you are assigning the address location to newObject
. Make a change in newObject
, and you change object
.
Fortunately, the spread operator ...
can be used to make a true copy of object
, one that can't be altered by changes to the copy.
Well, it's actually not QUITE that simple, I'm afraid. Let's see why.
const object = {
One_A: true,
One_B: true,
One_C: {
Two_A: true,
Two_B: {
Three_A: true,
},
},
}
const newObject = { ...object }
newObject.One_A = false
newObject.One_B = false
newObject.One_C.Two_A = false
newObject.One_C.Two_B.Three_A = false
console.log("newObject: ", newObject)
console.log("object: ", object)
And the result:
newObject: {
One_A: false,
One_B: false,
One_C: { Two_A: false, Two_B: { Three_A: false } }
}
object: {
One_A: true,
One_B: true,
One_C: { Two_A: false, Two_B: { Three_A: false } }
}
Ok, we did pretty well with not changing the top-level elements, but unfortunately, the nested objects at level 2 and 3 WERE changed.
The reason is this:
The spread operator only creates a new address location for the top-level elements. Any nested objects of newObject
are still at the same address locations as the nested objects of object
.
This means that we need to apply the spread operator at every level we want make a true value copy. So what would that look like?
const object = {
One_A: true,
One_B: true,
One_C: {
Two_A: true,
Two_B: {
Three_A: true,
},
},
}
const newObject = { ...object, One_C: { ...object.One_C } }
newObject.One_A = false
newObject.One_B = false
newObject.One_C.Two_A = false
newObject.One_C.Two_B.Three_A = false
console.log("newObject: ", newObject)
console.log("object: ", object)
The output:
newObject: {
One_A: false,
One_B: false,
One_C: { Two_A: false, Two_B: { Three_A: false } }
}
object: {
One_A: true,
One_B: true,
One_C: { Two_A: true, Two_B: { Three_A: false } }
}
This is better - we managed to protect level 2 of object
, but we still have to protect the level 3 nested object. And this is what that looks like:
const object = {
One_A: true,
One_B: true,
One_C: {
Two_A: true,
Two_B: {
Three_A: true,
},
},
}
const newObject = {
...object,
One_C: { ...object.One_C, Two_B: { ...object.One_C.Two_B } },
}
newObject.One_A = false
newObject.One_B = false
newObject.One_C.Two_A = false
newObject.One_C.Two_B.Three_A = false
console.log("newObject: ", newObject)
console.log("object: ", object)
And FINALLY:
newObject: {
One_A: false,
One_B: false,
One_C: { Two_A: false, Two_B: { Three_A: false } }
}
object: {
One_A: true,
One_B: true,
One_C: { Two_A: true, Two_B: { Three_A: true } }
}
As you can see, it starts to get quite messy the more levels of nested objects you have. Fortunately, there are several JavaScript libraries such as Immer that make deep-cloning nested objects fairly intuitive. But if you find yourself having to rely on only using the spread operator, going step-by-step through the layers is the best way to avoid mistakes. Ask yourself:
First, which objects do I want to protect?
object, One_C:, Two_B:
Next, set up the nesting structure with curly braces:
{ object, One_C: { , Two_B: {} } }
Finally add the spread operators, making sure you access each object:
const newObject = {
...object,
One_C: { ...object.One_C, Two_B: { ...object.One_C.Two_B } }
}
Remember that the top level is protected by the spread operator. ...object
protects level 1, ...object.One_C
protects level 2, and ...object.One_C.Two_B
protects level 3.
Understanding how to deeply-clone nested objects is essential for so many JavaScript tasks, particularly when we have to avoid mutating state objects in frameworks such as React/Redux. I hope you find this brief illustration of how to use the spread operator to get to all levels of your object helpful.
Happy coding!
Top comments (1)
Great article, I recently faced an issue with object copying and was searching for the reason behind the problem. I found this article, which is exactly what I needed!