Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I deep clone an object in React?

let oldMessages = Object.assign({}, this.state.messages); // this.state.messages[0].id = 718  console.log(oldMessages[0].id); // Prints 718  oldMessages[0].id = 123;  console.log(this.state.messages[0].id); // Prints 123 

How can I prevent oldMessages to be a reference, I want to change the value of oldMessages without changing the value of state.messages

like image 805
SpaceDogCS Avatar asked Feb 09 '18 17:02

SpaceDogCS


People also ask

What is object deep clone?

A deep copy of an object is a copy whose properties do not share the same references (point to the same underlying values) as those of the source object from which the copy was made.

How do you deep copy an object in es6?

If you simply want to deep copy the object to another object, all you will need to do is JSON. stringify the object and parse it using JSON. parse afterward. This will essentially perform deep copying of the object.


2 Answers

You need to make a deep copy. Lodash's cloneDeep makes this easy:

import cloneDeep from 'lodash/cloneDeep'; const oldMessages = cloneDeep(this.state.messages); oldMessages[0].id = 123; 
like image 74
AryanJ-NYC Avatar answered Oct 04 '22 11:10

AryanJ-NYC


First let's clarify the difference between shallow and deep clone:

A shallow clone is a clone that has its primitive properties cloned but his REFERENCE properties still reference the original.

Allow me to clarify:

let original = {   foo: "brlja",   howBigIsUniverse: Infinity,   mrMethodLookAtMe: () => "they call me mr. Method",   moo: {    moo: "MOO"   } };    // shallow copy   let shallow = Object.assign({}, original);   console.log(original, shallow); // looks OK    shallow.moo.moo = "NOT MOO";    console.log(original, shallow); // changing the copy changed the original 

Notice how changing the shallow copy's not primitive property's inner properties REFLECTED on the original object.

So why would we use shallow copy?

  • It is definitely FASTER.
  • It can be done in pure JS via 1 liner.

When would you use shallow copy?

  • All of your object's properties are primitives
  • You are making a partial copy where all your copied properties are primitives
  • You don't care about the fate of the original (is there a reason to copy and not use that one instead?)

Oke, let's get into making a propper (deep) copy. A deep copy should obviously have the original object coped into the clone by value, not references. And this should persist as we drill deeper into the object. So if we got X levels deep nested object inside of the original's property it should still be a copy not a reference to the same thing in memory.

What most people suggest is to abuse the JSON API. They think that turning an object into a string then back into an object via it will make a deep copy. Well, yes and NO. Let's attempt to do just that.

Extend our original example with:

  let falseDeep = JSON.parse(JSON.stringify(original));   falseDeep.moo.moo = "HEY I CAN MOO AGAIN";   console.log(original, falseDeep); // moo.moo is decoupled 

Seems ok, right? WRONG! Take a look at what happened to the mrMethodLookAtMe and howBigIsUniverse properties that I sneaked in from the start :)

One gives back null which is definitely not Infinity and the other one is GONE. That is no bueno.

In short: There are problems with "smarter" values like NaN or Infinity that get turned to null by JSON API. There are FURTHER problems if you use: methods, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays as your original object's properties.

In short. If you tell me you are a mid-level JS developer and you suggest I make deep copies of an object via JSON API, you will be offered a junior position. If you claim you are a senior and suggest JSON API is the correct answer I will tell you that the interview is over :)

Why? Well this produces some of the nastiest to track bugs out there.. I have nightmares tracking the disappearing methods or type being turned to another (which passed someone's bad input parameter check but then couldn't produce a valid result) before Typescript became a thing.

Time to wrap this up! So what is the correct answer?

  • You write your own implementation of a deep copy. I like you but please don't do this when we have a deadline to meet.
  • Use a deep cloning function provided to you by the library or framework you already use in the project.
  • Lodash's cloneDeep

Many people still use jQuery. So in our example (please put import where it belongs, on top of the file):

import jQ from "jquery";  let trueDeep = jQ.extend(true, original, {}); console.log(original, trueDeep); 

This works, it makes a nice deep copy and is a one-liner. But we had to import the entire jQuery. For some its already there but for me I tend to avoid it since it is overbloated and has terribly incosistent naming.

Angular users can use angular.copy(). For your project dependencies, you can google if it is in there :)

But what if my framework/library does not have a similar function?

You can use my personal SUPERSTAR among JS libraries (I am not involved in the project, just a big fan) - Lodash (or _ for friends).

So extend our example with (again, mind the position of import):

import _ from "lodash"; // cool kids know _ is low-dash var fastAndDeepCopy = _.cloneDeep(objects); console.log(original, lodashDeep); 

It is a simple oneliner, it works, it is fast.

This is pretty much it :)

Now you know the difference between shallow and deep copy in JS. You realize JSON API abuse is just that, abuse and not a true solution. If you are using jQuery or Angular already you now know there is a solution already there for you. If not you can write your own or consider using lodash.

The entire example can be found here: codesandbox - entire example

like image 32
DanteTheSmith Avatar answered Oct 04 '22 10:10

DanteTheSmith