4

I have a list and I am using a for loop to loop through it. The structure looks like this:

salesLists: { 
  1: [ [], [], [] ]
  2: [ [], [] ]
}

And html:

<div v-for="(saleLists, index) in salesLists">
    <my-comp v-for="(item, i) in saleLists" :key="i" :index="parseInt(i)+1"></my-comp>
</div>

Now, I am trying to remove items from salesLists[1] array. I have a button for that and @click="removeForm":

removeForm(e) {
        var index = parseInt(e.target.getAttribute('data-index')) - 1 // = 2
        var client = e.target.getAttribute('data-client')             // = 1
        //Vue.delete(this.salesLists[client], index);
        this.salesLists[client].splice(index, 1)
        this.$forceUpdate()
}

It removes it, however, as I didn't specify any keys and it's just empty arrays (i assume), it is not removing the right element from the DOM. It removes index of 2, but as it is v-for looping through the item, and count reduces, it only removes the last item in the end.

What is the proper way of overcome this issue? :/

Here is a Fiddle: https://jsfiddle.net/8rvfz40n/ try writing different values for each input field and removing the middle one, you'll see it will remove the last one

1
  • And why do you think it removes wrong item? I see that it works properly and removes correct one. jsfiddle.net/8rvfz40n/1 Commented Aug 12, 2017 at 21:57

3 Answers 3

13
<div v-for="(saleLists, index) in salesLists">
    <my-comp v-for="(item, i) in saleLists" :key="i" :index="parseInt(i)+1"></my-comp>
</div>

The use of the index as key is the problem, when you delete a item from the middle the index that is lost is the last.

In my case the solution that I found is add a unique "Hash" to the items, like an ID, but if the items are news, the ID is null.

The hash that I use is a timestamp:

Hash: new Date().getTime()

And then:

 <div v-for="(saleLists, index) in salesLists">
     <my-comp v-for="(item, i) in saleLists" :key="item.Hash" :index="parseInt(i)+1"></my-comp>
 </div>
Sign up to request clarification or add additional context in comments.

1 Comment

Nice approach. I'd love to hear what everyone else thinks
3

this one trips up a lot of people I think.

I've written an answer to this in the vue forum a while back at https://forum.vuejs.org/t/solved-array-of-components-wrong-after-element-remove/11866/3

so the problem is this

you have an array [rec1,rec2,rec3]

the keys for that array are 0, 1, 2

if you remove item at index of 1 you'd get an array with values [rec1, rec3], however the keys would be [0, 1], as the array does not skip the index after you remove it. Once you're in the template drawing it, since you don't have the key defined, the change the component sees is that the key or index 2 is missing, which is the last item, so it removes it.

to solve that, you need to find a different way to make sure you're targeting the intended item

https://jsfiddle.net/8rvfz40n/2/

in your case, using the item list instead of the index i will remove the intended item

<div id="app">
  <div v-for="lists in xLists">
    <my-comp v-for="(list, i) in lists" :list="list"></my-comp>
  </div>
</div>

I should mention that another alternative is to store the unique key somehow inside the array, but as you you can imagine, that can be harder to maintain

4 Comments

Thanks a lot! It works. I changed the :key to list and it worked; however now, I started getting error : Avoid using non-primitive value as key, use string/number value instead. - I guess because it is an array. What do you suggest? The error occurs also in your fiddle. Should I just ignore it?
the best way is to use key-value pairs in objects instead of using arrays, that way you control the relation.
@Daniel If you go with the object approach, are you supposed to use it in conjunction with Vue.delete and Vue.set instead of Array.splice and push?
you may be able to get away without it, but for reactivity to work properly, you're better off doing that.
0

Your issue is actually pretty straightforward: you lose the reference to the object key in xLists in your first loop. If you store the object key and pass it on as a prop, you will retain that reference:

<div v-for="(saleLists, index) in salesLists">
    <!-- index will refer to the object key -->
    <my-comp v-for="(item, i) in saleLists" :key="i" :index="i+1" :sales-list-index=":index"></my-comp>
</div>

And you can simply retrieve the prop salesListIndex and use it as the key to point to the correct nested array in your object. It is not clear how the component in your actual example is written, but with reference to your fiddle (I have used alphabetical keys so that you can tell if it's an object key or an array key, but implementation-wise it is identical):

Vue.component('my-comp', {
  props: ['index', 'value', 'listKey'],
  template: `
  	<div>
    	<p>xListsKey: {{ listKey }}, index: {{ index }}</p>
    	<input :value="value" /> 
      <button :data-index="index" @click="remove">del </button>
    </div>
  `,

  methods: {
    remove(e) {
      var index = e.target.getAttribute('data-index');
      this.$parent.xLists[this.listKey].splice(index, 1)
    }
  }
})

new Vue({
  el: '#app',
  data: {
    xLists: {
      'aa': [
        ['lorem'],
        ['ipsum'],
        ['dolor']
      ],
      'bb': [
        ['foo'],
        ['bar']
      ]
    }
  }
})
<script src="https://unpkg.com/vue"></script>

<div id="app">
  <div v-for="(lists, listKey) in xLists" :key="listKey">
    <my-comp v-for="(list, i) in lists" :key="i" :index="i" :value="list" :list-key="listKey"></my-comp>
  </div>
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.