Referencing the live demo code here:
https://codesandbox.io/s/vue-template-r26tg
Let's say I have a Vuex store with the following data:
const store = new Vuex.Store({
state: {
categories: [
{
name: "Category A",
items: [{ name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" }]
},
{
name: "Category B",
items: [{ name: "Item A" }, { name: "Item B" }, { name: "Item C" }]
},
{
name: "Category C",
items: [{ name: "Item !" }, { name: "Item @" }, { name: "Item #" }]
}
]
}
});
And I have an App.vue
, Category.vue
and Item.vue
that are set up so that they are rendered like so:
//App.vue
<template>
<div id="app">
<Category v-for="(category, index) in categories" :category="category" :key="index"/>
</div>
</template>
<script>
export default {
components: { Category },
computed: {
...mapState(["categories"])
}
};
</script>
//Category.vue
<template>
<div class="category">
<div class="header">{{ category.name }}</div>
<Item v-for="(item, index) in category.items" :item="item" :key="index"/>
</div>
</template>
<script>
export default {
components: { Item },
props: {
category: { type: Object, required: true }
}
};
</script>
//Item.vue
<template>
<div class="item">
<div class="name">{{ item.name }}</div>
<div class="delete" @click="onDelete">✖</div>
</div>
</template>
<script>
export default {
props: {
item: { type: Object, required: true }
},
methods: {
onDelete() {
this.$store.commit("deleteItem", this.item);
}
}
};
</script>
In other words, App.vue
gets the list of categories from Vuex, then passes it down to Category.vue
as a prop for each category, then Category.vue
passes down category.items
to Item.vue
as a prop for each item.
I need to delete an item when the delete button next to it is clicked:
However, at the Item.vue
level, I only have access to the item
, but not the category
. If I send the item
to Vuex, I have no way of telling which category
it belongs to. How do I get a reference to the category
so that I can delete the item from it using Vuex?
I can think of two ways:
Add a parent reference back to the category
for each item
. This is undesirable not only because I'd have to massage the item
data, but also because it introduces a circular reference that I'd rather not have to deal with in other parts of the app.
Emit an event from Item.vue
up to Category.vue
and let Category.vue
handle the Vuex call for deletion. This way the category and the to-be-deleted item are both known.
Is there a better way of handling this kind of deletion?
I'd strongly recommend (2). In general, if you can create a component which takes props and emits events without having other side effects (API calls, Vuex mutations, etc.) that's usually the correct path. In this case, you can probably even push the event all the way back to the parent's parent.
Where shared state (Vuex) really helps is when you have two or more components which are far away from each other in the DOM tree. E.g. imagine a header with a count of the total items. That degree of spatial separation may exist in your app, but it doesn't in this simple example.
An additional benefit to emitting an event here is that you care more easily use tools like storybook without having to deal with any Vuex workarounds.