0

I think it's easier for you to just see the relevant VueJS code and then I can explain it:

new Vue({
    el: '#app',
    data: {
        history: [
            {name: 'red', value: '#f00'},
            {name: 'not red', value: '#ff0'},
            {name: 'also not red', value: '#f0f'},
        ],
    },
    components: {
        ColorItem: {
            template:
                `<div>
                    <input :value="name">
                    <div class="color-preview" :style="{backgroundColor:hex}"></div>
                    <span v-html="hex"></span>
                    <button @click="$emit('remove')">
                        <i class="fas fa-trash"></i>
                    </button>
                </div>`,
            props: 
                ['mode', 'hex', 'name'],
            methods: {
                removeColor: function(index) {
                    console.log(index);
                    this.history.splice(index, 1);
                }
            },
        }
    },
    // ...
}

I have objects (representing colors with names and values) in an array in a variable called history in my Vue app. I'm using v-for to create a new color-item component for each item in history:

<div v-for="(item, index) in history" :key="item.value">
    <color-item mode="history" :name="item.name" :hex="item.value" @remove="removeColor(index)">
    </color-item>
</div>

I'm trying to delete the color from the list, and I saw this beautiful example of how to use vue to remove items from a list, and it uses their position and splices it. I also saw this SO answer on getting the position using the map function, however pos is undefined for me, because e.hex is undefined, and using the inspector I think it's because Vue uses some sort of getter under the hood and doesn't just have the data there for me.

Before someone tells me to use the component template in the v-for loop, I need the template so I can reuse this for other lists of colors (for example, favorites).

I'm very new to Vue so pardon my improper wordings, and I appreciate all the help I can get learning this framework.

Justin
  • 945
  • 12
  • 26

2 Answers2

1

The better approach here is to use $emit from the child component and listening to the event in the parent component.

You should pass pos as an event then change the history data directly in parent component

In your removeColor function:

this.$emit('remove-color', pos)

Then in parent component you should have:

<ColorItem v-on:remove-color="removeColorFromHistory" />

And change your data in methods of the parent component:

methods: {
  removeColorFromHistory(pos) {
    this.history.splice(pos, 1);
  }
}

(see documents)

$parent is meant for handling of edge cases, meaning unusual situations that sometimes require bending Vue’s rules a little. Note however, that they all have disadvantages or situations where they could be dangerous. *

tony19
  • 125,647
  • 18
  • 229
  • 307
Faraz A.
  • 146
  • 1
  • 12
  • How can I pass the `pos` position variable from my template? I have the component HTML defined in Vue using a template. – Justin Aug 15 '20 at 16:37
  • I updated my question using emit as suggested, however it still doesn't work. Any idea why? – Justin Aug 15 '20 at 17:21
  • @Justin you have to have a template in the parent component where you listen to the event emited from the child `` this will call the METHOD in the parent, your method is currently in the child component. – Faraz A. Aug 16 '20 at 05:35
1

Generally, the child component should not be manipulating data of the parent component directly, especially via this.$parent.whatever. You should keep the boundaries of the components distinct, otherwise you'll make them tightly-coupled.

All the child component needs to do is emit a remove event that tells the parent that it should remove that item from its own data (which the parent owns).

In the child component:

<button @click="$emit('remove')">Remove</button>

Then in the parent component:

<div v-for="item of history">
  <color-item :hex="item.hex" @remove="removeItem(item)"/>
</div>
methods: {
  removeItem(item) {
    this.history = this.history.filter(otherItem => otherItem !== item)

    // or
    this.history.splice(this.history.indexOf(item), 1)
  }
}

The event handler for the remove event passes in the item to remove as an argument.

Since the parent component "owns" the history array, it should be the only component that mutates it. Once you start allowing random components to mutate data they do not own, then you start getting spaghetti code and it can be difficult to track down why a particular mutation happened and who mutated it.

Here is an example:

Vue.component('color-item', {
  template: '#color-item',
  props: ['name', 'hex'],
})

new Vue({
  el: '#app',
  data: {
    items: [
      { name: 'red', value: '#f00' },
      { name: 'green', value: '#0f0' },
      { name: 'blue', value: '#00f' },
    ],
  },
  methods: {
    removeItem(item) {
      this.items.splice(this.items.indexOf(item), 1)
    },
  },
})
.name {
  display: inline-block;
  min-width: 50px;
}

.preview {
  display: inline-block;
  width: 15px;
  height: 15px;
  margin-right: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<template id="color-item">
  <div>
    <span class="name">{{ name }}</span>
    <div class="preview" :style="{ backgroundColor: hex }"></div>
    <button @click="$emit('remove')">Remove</button>
  </div>
</template>

<div id="app">
  <div v-for="item of items">
    <color-item :name="item.name" :hex="item.value" @remove="removeItem(item)"></color-item>
  </div>
</div>
Decade Moon
  • 32,968
  • 8
  • 81
  • 101
  • Thank you for the detailed response. I tried this and it didn't work; I believe it's something to do with needing the object's index in the array, whereas you're just treating the color items like primitives. – Justin Aug 15 '20 at 16:38
  • I tried passing in the index by using `v-for="(item, index) in history"` but it doesn't call the function – Justin Aug 15 '20 at 17:18
  • I'm not treating the color items like primitives. `item` is an object, no? This should work. – Decade Moon Aug 15 '20 at 23:53
  • I think the function isn't called for some reason then... Could you look at my updated code? – Justin Aug 16 '20 at 03:49