58
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

SpaceDogCS
  • 2,808
  • 3
  • 20
  • 49

4 Answers4

91

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;
Dharman
  • 30,962
  • 25
  • 85
  • 135
AryanJ-NYC
  • 2,529
  • 2
  • 20
  • 27
50

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. Well, 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.

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. Which is fine if it is already being used in project, but I tend to avoid it since it is over-bloated and has terribly inconsistent naming.

Similarly, AngularJS users can use angular.copy().

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 AngularJS 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

DanteTheSmith
  • 2,937
  • 1
  • 16
  • 31
  • 2
    beatiful answer, one thing is that at the browser, the date seens to had no difference after abusing of the JSON API (it is all timestamps), but I undestand why it is an abuse, maybe changing the Date example to an empty class ou function as you said at the end – SpaceDogCS May 28 '21 at 12:53
  • @SpaceDogCS Ty for the edit and your kind comment. Misuse would be a better word for it now that I think about it. "(it is all timestamps)," looks like it depends what browser I am seeing the difference in console and trying to do math with one will work but with other will break. I could edit a lot more stuff in but I made a list (and bolded it) and my general intention is to explain that you should not do it rather than giving the impression of how you should do it while minding this list of pitfalls. It could be I missed something from that list but it is not the focal point :) – DanteTheSmith Jun 01 '21 at 15:49
  • 1
    It seems using the JSON api is totally acceptable if you know your hierarchy is never going to contain anything but strings and integers. It's not hard to write an abstracted function that just abuses the JSON api and then rewrite it and use a heavier duty deepcopy method if it's needed. As long as it's separated, you're good if you ever need to worry about expansion that might break it. Also, I imagine deep copy methods are significantly slower than JSON parsing, or am I wrong about that? True deepcopy requires recursing the entire structure. Does JSON? – mas Jun 22 '21 at 14:11
  • 1
    I having hard time to undertand "fancy naming OOP peasants use to scare away the glorious functional programming master race" so it means "Methods, is function inside object or class" – rickvian Aug 03 '21 at 07:34
  • Ty guys for all the comments, I have revisited the answer and improved it by making it show more of the problems in less code and words. Also, I have removed the other option when to use shallow copy since I think it brings more trouble than benefit as the codebase ages. – DanteTheSmith Aug 03 '21 at 09:15
11

Try Using

let tempVar = JSON.parse(JSON.stringify(this.state.statename))
Anjali
  • 143
  • 1
  • 6
  • 8
    While this could work, I suspect it runs into issues if your data structure can't be represented by JSON (such as one of your keys being a date object). – RonLugge Jan 24 '20 at 22:49
  • 1
    This is very unclean, slow, and might have issues if the JSON serialization/deserialization skips some parts. – mir88 Oct 13 '20 at 11:11
  • 4
    Please DON'T. JSON API is NOT intended to serve this purpose and there are many pitfalls to this method. Please check my answer and discover how to do it properly. – DanteTheSmith Jun 15 '21 at 14:11
3

What actually you are doing

let oldMessages = Object.assign({}, this.state.messages);

is a shallow copy which is similar to {...this.state.message} with spread operator.

Object has its own reference in memory to destroy it you can use JSON.parse (JSON.stringify(object)) no matter how nested key it has, it will remove the reference of the object and you will get a new object.

This concept is called a deep copy or deep clone.

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • 1
    DON'T use JSON API to make deep copies of data (especially if you don't know how the said data looks). I have explained the many pitfalls this leads to. – DanteTheSmith Jun 15 '21 at 14:13