madsongr
madsongr

Reputation: 785

Vue js cannot splice item from nested v-for

I'm starting with Vue.js and I read about splice() according to item id/index. It works fine if I try to splice() an item in "tours" v-for:

data:{
   tours: null,
},
methods: {
  deleteTour: function (id) {
    if (confirm('Are you sure?')) {
        const index = this.tours.findIndex(item => item.id === id);
      if (~index) {
         axios.post('deleteTour.php', { "token": token, "tourID": id }).then((response) => {
           this.deleted = response;
           console.log(this.deleted);

           // splice tour
           this.tours.splice(index, 1);
         }).catch((error) => {
           this.errored = error;
           console.log(this.errored);
         });
       }
    }
  },
}

html

<div id="app" class="row mb-50">
   <div v-for="(item, index) in tours" v-bind:key="item.id" id="tours" class="col-md-12 mb-30">
      <ul class="pics-list">
         <li v-for="(image, index) in item.images" ref="deleteItem" :class="{marginRightOpen: editingTour}">
            <span :hidden="editingTour !== item.id" class="badge" @click="deleteImage(image.imageID, item.id, index)">
            <i class="fa fa-fw fa-times-circle"></i>
            </span>
            <div class="pics-list-image-container img-fluid cursor-pointer" v-bind:style="{'background-image': 'url(http://localhost/tours/'+image.image + ')' }" @click="openModal = true, showModal(image.image)">
            </div>
         </li>
         <li v-if="urls && editingTour == item.id" v-for="(url, key) in urls" :key="key">
            <div id="preview" :ref="'url'" class="pics-list-image-container img-fluid">
            </div>
         </li>
      </ul>
   </div>
</div>

But I'm not able to splice() a list item within a nested v-for. I tried using ref in many ways but it didn't work.

deleteImage: function (id, tourID, index) {
   if (confirm('Are you sure?')) {

      // it could be done here
      this.$refs.deleteItem.splice(index, 1);

      axios.post('deleteImage.php', { "token": token, "tourID": tourID, "imageID": id })
           .then((response) => {
               console.log(response);

               // or here
               this.$refs.deleteItem.splice(index, 1);
        }).catch((error) => {
               this.errored = error;
               console.log(this.errored);
        });
      }
},

I want to remove the whole first list item that contains the badge icon. Is it possible to do that?

Upvotes: 1

Views: 524

Answers (2)

Cato Minor
Cato Minor

Reputation: 3099

If we use different variable names for the v-for indexes in the template, we can reference these difference indexes in deleteImage:

<div id="app" class="row mb-50">
   <div v-for="(item, indexItem) in tours" v-bind:key="item.id" id="tours" class="col-md-12 mb-30">
      <ul class="pics-list">
         <li v-for="(image, indexImage) in item.images" ref="deleteItem" :class="{marginRightOpen: editingTour}">
            <span :hidden="editingTour !== item.id" class="badge" @click="deleteImage(image.imageID, item.id, indexItem, indexImage)">
            <i class="fa fa-fw fa-times-circle"></i>
            </span>
            <div class="pics-list-image-container img-fluid cursor-pointer" v-bind:style="{'background-image': 'url(http://localhost/tours/'+image.image + ')' }" @click="openModal = true, showModal(image.image)">
            </div>
         </li>
         <li v-if="urls && editingTour == item.id" v-for="(url, key) in urls" :key="key">
            <div id="preview" :ref="'url'" class="pics-list-image-container img-fluid">
            </div>
         </li>
      </ul>
   </div>
</div>

Then in deleteImage:

deleteImage: function (id, tourID, indexItem, indexImage) {
   if (confirm('Are you sure?')) {

      // it could be done here
      // this.$refs.deleteItem.splice(index, 1);
      this.tours[indexItem].images.splice(indexImage,1)

      axios.post('deleteImage.php', { "token": token, "tourID": tourID, "imageID": id })
           .then((response) => {
               console.log(response);

               // or here
               this.$refs.deleteItem.splice(index, 1);
        }).catch((error) => {
               this.errored = error;
               console.log(this.errored);
        });
      }
},

In general, $refs are not an ideal solution as per the Vue documentation:

$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.

Upvotes: 1

madsongr
madsongr

Reputation: 785

Based on Cato Minor comment I changed

deleteImage: function (id, tourID, index) {
    this.$refs.deleteItem.splice(index, 1);

to:

deleteImage: function (id, tourID, i, index) {
    this.tours[index].images.splice(i, 1);

where i is the image index from <li v-for="(image, i) in item.images" and index is the tour index from <div v-for="(item, index) in tours"

Upvotes: 0

Related Questions