Vuejs and Vue.set(), update array
Asked Answered
W

5

113

I'm new to Vuejs. Made something, but I don't know it's the simple / right way.

what I want

I want some dates in an array and update them on a event. First I tried Vue.set, but it dind't work out. Now after changing my array item:

this.items[index] = val;
this.items.push();

I push() nothing to the array and it will update.. But sometimes the last item will be hidden, somehow... I think this solution is a bit hacky, how can I make it stable?

Simple code is here:

new Vue({
  el: '#app',
  data: {
  	f: 'DD-MM-YYYY',
    items: [
      "10-03-2017",
      "12-03-2017"
    ]
  },
  methods: {
    
    cha: function(index, item, what, count) {
    	console.log(item + " index > " + index);
      val = moment(this.items[index], this.f).add(count, what).format(this.f);
  		this.items[index] = val;
      this.items.push();
      console.log("arr length:  " + this.items.length);
    }
  }
})
ul {
  list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
  <ul>
    <li v-for="(index, item) in items">
    <br><br>
      <button v-on:click="cha(index, item, 'day', -1)">
      - day</button>
      {{ item }}
      <button v-on:click="cha(index, item, 'day', 1)">
      + day</button>
    <br><br>
    </li>
  </ul>
</div>
Willis answered 15/3, 2017 at 11:2 Comment(0)
A
62

VueJS can't pickup your changes to the state if you manipulate arrays like this.

As explained in Common Beginner Gotchas, you should use array methods like push, splice or whatever and never modify the indexes like this a[2] = 2 nor the .length property of an array.

new Vue({
  el: '#app',
  data: {
    f: 'DD-MM-YYYY',
    items: [
      "10-03-2017",
      "12-03-2017"
    ]
  },
  methods: {

    cha: function(index, item, what, count) {
      console.log(item + " index > " + index);
      val = moment(this.items[index], this.f).add(count, what).format(this.f);

      this.items.$set(index, val)
      console.log("arr length:  " + this.items.length);
    }
  }
})
ul {
  list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
  <ul>
    <li v-for="(index, item) in items">
      <br><br>
      <button v-on:click="cha(index, item, 'day', -1)">
      - day</button> {{ item }}
      <button v-on:click="cha(index, item, 'day', 1)">
      + day</button>
      <br><br>
    </li>
  </ul>
</div>
Allowable answered 15/3, 2017 at 11:10 Comment(3)
As said in the docs, $set is just a pretty .splice(). This makes sense because like they say in the docs you should modify the array using it's methods.Allowable
Pls don't combine tab indents with space indents, the size of a tab is apparently different on the SE app, making it hard to read your codeUsurpation
In my case the ES6 spread-operator for the push operation does not work proberly with vueHarriettharrietta
G
133

EDIT 2

  • For all object changes that need reactivity use Vue.set(object, prop, value)
  • For array mutations, you can look at the currently supported list here

EDIT 1

For vuex you will want to do Vue.set(state.object, key, value)


Original

So just for others who come to this question. It appears at some point in Vue 2.* they removed this.items.$set(index, val) in favor of this.$set(this.items, index, val).

Splice is still available and here is a link to array mutation methods available in vue link.

Gruver answered 26/7, 2017 at 20:4 Comment(8)
What string do I need to provide as the 'index' ?Larisa
@Larisa can you clarify what you are trying to do? If it is an array it should be a number for the index. A string would be if you are setting a field on an object.Gruver
Ah ok, my IDE insisted it had to be a string, must be a glitch.Larisa
Vue.set is not for vuex. And this.$set deprecated.Luckily
@Luckily link? Vue reactivity page still shows this.$set as a suggested method. Also, the vuex docs still say the proper way to add reactive properties is Vue.set. vuex.vuejs.org/guide/…Gruver
@MartinCalvert It says "when adding property to object". That is Vue.set's main purpose, not related to only vuex. Deprecation, i read it on the link but now reading again, i don't fully understand it. #36671606Luckily
@Luckily oh I see, yea so my example might have bad names, but I am using it in Vuex to add new properties to an object, Vue.set(some_object_in_state_tree, new_prop, new_value). The deprecated part is a good call out tho I'll edit my answer to use the globals.Gruver
"For all object changes" -- You only need to use Vue.set on objects if the key doesn't already exist. If you are sure that the object already has the key, then you can simply do object[key] = value.Juncture
A
62

VueJS can't pickup your changes to the state if you manipulate arrays like this.

As explained in Common Beginner Gotchas, you should use array methods like push, splice or whatever and never modify the indexes like this a[2] = 2 nor the .length property of an array.

new Vue({
  el: '#app',
  data: {
    f: 'DD-MM-YYYY',
    items: [
      "10-03-2017",
      "12-03-2017"
    ]
  },
  methods: {

    cha: function(index, item, what, count) {
      console.log(item + " index > " + index);
      val = moment(this.items[index], this.f).add(count, what).format(this.f);

      this.items.$set(index, val)
      console.log("arr length:  " + this.items.length);
    }
  }
})
ul {
  list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.11/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<div id="app">
  <ul>
    <li v-for="(index, item) in items">
      <br><br>
      <button v-on:click="cha(index, item, 'day', -1)">
      - day</button> {{ item }}
      <button v-on:click="cha(index, item, 'day', 1)">
      + day</button>
      <br><br>
    </li>
  </ul>
</div>
Allowable answered 15/3, 2017 at 11:10 Comment(3)
As said in the docs, $set is just a pretty .splice(). This makes sense because like they say in the docs you should modify the array using it's methods.Allowable
Pls don't combine tab indents with space indents, the size of a tab is apparently different on the SE app, making it hard to read your codeUsurpation
In my case the ES6 spread-operator for the push operation does not work proberly with vueHarriettharrietta
H
24

As stated before - VueJS simply can't track those operations(array elements assignment). All operations that are tracked by VueJS with array are here. But I'll copy them once again:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

During development, you face a problem - how to live with that :).

push(), pop(), shift(), unshift(), sort() and reverse() are pretty plain and help you in some cases but the main focus lies within the splice(), which allows you effectively modify the array that would be tracked by VueJs. So I can share some of the approaches, that are used the most working with arrays.

You need to replace Item in Array:

// note - findIndex might be replaced with some(), filter(), forEach() 
// or any other function/approach if you need 
// additional browser support, or you might use a polyfill
const index = this.values.findIndex(item => {
          return (replacementItem.id === item.id)
        })
this.values.splice(index, 1, replacementItem)

Note: if you just need to modify an item field - you can do it just by:

this.values[index].itemField = newItemFieldValue

And this would be tracked by VueJS as the item(Object) fields would be tracked.

You need to empty the array:

this.values.splice(0, this.values.length)

Actually you can do much more with this function splice() - w3schools link You can add multiple records, delete multiple records, etc.

Vue.set() and Vue.delete()

Vue.set() and Vue.delete() might be used for adding field to your UI version of data. For example, you need some additional calculated data or flags within your objects. You can do this for your objects, or list of objects(in the loop):

 Vue.set(plan, 'editEnabled', true) //(or this.$set)

And send edited data back to the back-end in the same format doing this before the Axios call:

 Vue.delete(plan, 'editEnabled') //(or this.$delete)
Hoover answered 11/12, 2019 at 15:58 Comment(0)
S
0

One alternative - and more lightweight approach to your problem - might be, just editing the array temporarily and then assigning the whole array back to your variable. Because as Vue does not watch individual items it will watch the whole variable being updated.

So you this should work as well:

var tempArray[];

tempArray = this.items;

tempArray[targetPosition]  = value;

this.items = tempArray;

This then should also update your DOM.

Scrummage answered 15/4, 2020 at 5:29 Comment(0)
A
0

I came here because I had a problem I have lots of times with vue, so leaving it here for future reference:

This is wrong / unresponsive:

this.items = resp.items; // got from api
for(let i = 0; i < this.items.length; i++){
  // if you add a new property here, it's not responsive later
  this.items[i].selected = false;
}

This is right / responsive:

const items = resp.items; // got from api
for(let i = 0; i < items.length; i++){
  // add a new property here
  items[i].selected = false;
}
// getters and setters are created for all properties
this.items = items;
Abelabelard answered 19/10, 2023 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.