30

I have a Vue.js app where I have a v-repeat on an array of items. I want to add a newItem to the list of items. When I try this.items.push(this.newItem) the object pushed is still bound to the input. Consider the below:

new Vue({
  el: '#demo',

  data: {
    items: [
      {
        start: '12:15',
        end: '13:15',
        name: 'Whatch Vue.js Laracast',
        description: 'Watched the Laracast series on Vue.js',
        tags: ['learning', 'Vue.js', 'Laracast', 'PHP'],
        note: "Vue.js is really awesome. Thanks Evan You!!!"
      },
      {
        start: '13:15',
        end: '13:30',
        name: "Rubik's Cube",
        description: "Play with my Rubik's Cube",
        tags: ['Logic', 'Puzzle', "Rubik's Cube"],
        note: "Learned a new algorithm."
      }
    ],
    newItem: {start: '', end: '', name: '', description: '', tags: '', note: ''}
  },

  methods: {
    addItem: function(e) {
      e.preventDefault();

      this.items.push(this.newItem);
    }
  }
});

The above will, as expected, push the object that is bound onto the items array. The problem is I want just a copy of the object so it will no longer change when the input changes. See this this fiddle. I know I can do:

addItem: function(e) {
  e.preventDefault();
  this.items.push({
    name:        this.newItem.name,
    start:       this.newItem.start,
    end:         this.newItem.end,
    description: this.newItem.description,
    tags:        this.newItem.tags,
    notes:       this.newItem.notes
  })
}

This works but is a lot of repetition.

The question: Is there a built in way to add just a copy of the object instead of the persistent object.

Elfayer
  • 4,411
  • 9
  • 46
  • 76
DutGRIFF
  • 5,103
  • 1
  • 33
  • 42
  • I understand there could be a columns array in data to generate the columns and it's models etc.. Also the tags field isn't kept as an array. I copied this from a project I am starting and half implemented it for an example. Just ignore these. – DutGRIFF Jun 01 '15 at 17:10

5 Answers5

55

See this issue on GitHub.

Shallow Clone

I was using jQuery's $.extend until Evan You pointed out there is an undocumented built it extend function Vue.util.extend that does a shallow clone. So what you could use is:

addItem: function(e) {
  e.preventDefault();

  this.items.push(Vue.util.extend({}, this.newItem));
}

See the updated Fiddle.

Deep Clone

When doing a shallow clone on an object that references other objects you copy the references to the external objects instead of cloning them. To clone the object completely do a Deep Clone.

For the deep clone, per Evan's suggestion in the first link, one could use: JSON.parse(JSON.stringify(object)). This can be seen between this fiddle and this fiddle.

If using lodash check out lodash cloneDeep. If using NPM check out clone-deep.

DutGRIFF
  • 5,103
  • 1
  • 33
  • 42
  • 10
    Evan's comment on that issue states that it is a shallow clone. Only the top level properties are cloned. – patricus Mar 22 '17 at 16:05
  • i tried clone-deep and it's working fine. I just wanted to make sure I have a sort of snapshot copy of an obj that had a vue component in there and _.cloneDeep and most libs and functions don't work well, but clone-deep did it for me. – Emmanuel Mahuni Aug 02 '22 at 09:31
  • 1
    I fixed clone-deep so it handles circular references, see https://github.com/emahuni/clone-deep.git – Emmanuel Mahuni Aug 02 '22 at 13:02
  • This article was a good read: https://code.tutsplus.com/articles/the-best-way-to-deep-copy-an-object-in-javascript--cms-39655 – DutGRIFF Jun 10 '23 at 11:40
14

This didn't work for me (vue 1.0.13). I used the following to create a copy without the data bindings:

this.items.push( JSON.parse( JSON.stringify( newItem ) ) );
user3698965
  • 141
  • 1
  • 4
8

You can use Vanilla JavaScript with Object.assign():

addItem: function(e) {
  e.preventDefault();

  this.items.push(Object.assign({}, this.newItem));
}

UPDATE:

You can also use Object Spread:

addItem: function(e) {
  e.preventDefault();

  this.items.push({...this.newItem});
}
Arthur Ronconi
  • 2,290
  • 25
  • 25
4

The top answer is wrong. Vue.util.extend has nothing to do with jQuery's extend. It's always a shallow clone. https://github.com/vuejs/vue/issues/1849#issuecomment-158770874

Object.assign and Spread operator are also shallow copy. see this https://scotch.io/bar-talk/copying-objects-in-javascript

Simply use the implementation of Ramda.js https://github.com/ramda/ramda/blob/v0.26.1/source/internal/_clone.js

_curry is not necessary if you don't want them.

Or check this MVA What is the most efficient way to deep clone an object in JavaScript?

Alvin Smith
  • 547
  • 5
  • 9
1

structuredClone can be used but there is a little gotcha. It cannot clone an Proxy which is the way Vue 3 implements reactivity. So you must first get the target and clone this:

import { toRaw } from 'vue'
...
const someCopy = structuredClone(toRaw(someObject))
Johan Dahl
  • 66
  • 1
  • 9