1

I have a Vue component and a root Vue instance. The instance contains an array of objects (for products) and the component is displayed in my HTML using a v-for loop for each product. This is what products.js looks like:

/**
 * Component to show products
 */
Vue.component('product', {
    props: ['product'],
    data: function() {
        return {
            localProduct: this.product
        };
    },
    template: `<div class="products">
                    <span>{{ localProduct.product }}</span>
                    <a href="javascript:void" v-on:click="remove">Remove</a>
                </div>`,
    methods: {
        remove: function() {

            var removeIndex = productsList.products.map(function(i) { return i.id; }).indexOf(this.localProduct.id);
            productsList.products.splice(removeIndex, 1);
        }
    }
});

/**
 * Instantiate root Vue instance
 */
var productsList = new Vue({
    el: '#products',
    data: {
        products: [{ id: 1, product: 'iPad' }, { id: 2, product: 'iPhone' }, { id: '3', product: 'AirPods' }]
    }
});

Now, the loop renders 3 DIVs for iPad, iPhone and AirPods. What's strange is, when I click the remove button for iPhone (productsList.products[1]), the HTML displays iPad and iPhone instead of iPad and AirPods (since we removed iPhone). I just can't figure out what's going on.

My array splice code seems to be working correctly as well. I console.logged the updated array after the splice function and it only included iPad and AirPods (correct) but strangely, the view is different! Can someone tell me what I'm doing wrong here? Thanks.

2 Answers 2

4

You should use the :key to keep track of the elements.

<product v-for="product in products"
         :product="product"
         :key="product.id"
         v-on:remove="removeProduct"></product>

I put together an example here.

/**
 * Component to show products
 */
Vue.component('product', {
    props: ['product'],
    data: function() {
        return {
            localProduct: this.product
        };
    },
    template: `<div class="products">
                    <span>{{ localProduct.product }}</span>
                    <a href="javascript:void" v-on:click="remove">Remove</a>
                </div>`,
    methods: {
        remove: function() {
            this.$emit('remove', this.localProduct.id);
        }
    }
});

/**
 * Instantiate root Vue instance
 */
var productsList = new Vue({
    el: '#products',
    data: {
        products: [{ id: 1, product: 'iPad' }, { id: 2, product: 'iPhone' }, { id: '3', product: 'AirPods' }]
    },
    methods: {
        removeProduct: function(id) {
      this.products = this.products.filter(function(p) {
        return p.id !== id;
      });
      }
    }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.min.js"></script>
<div id="products">
<product v-for="product in products"
         :product="product"
         :key="product.id"
         v-on:remove="removeProduct"></product>
</div>

I also did a bit of cleanup to your code like using filter() instead of splice(). And having the child component emit an event that the parent acts upon instead of the child directly changing the data on the parent.

To learn more about list rendering check out the docs.

Sign up to request clarification or add additional context in comments.

1 Comment

Great answer. Try to avoid fiddles when they aren't necessary.
1

If you change localProduct from a data property to a computed one, you can keep the rest of your code identical and it seems to work. Just pop this guy in place of data, in between props and template.

computed: {
    localProduct: function() {
        return this.product
    }
},

2 Comments

This worked! Any explanation for why this works as opposed to my code?
I think it has something to do with the Computed Caching section of the Vue documentation, but I'm not 100% sure

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.