I'm using pinia to store data in my Vue app. There is a component called ListTag:
<template>
<div
v-for="(tag, index) in visibleTagItems"
:key="index"
class="q-mx-none q-px-none"
>
<q-chip
:removable="tagsWantToggle"
size="8px"
outline
:color="tag.color"
:class="`co-${tag.color}1 text-${tag.color} `"
class="q-ma-none q-mr-sm"
@remove="removeTag(index)"
>
{{ wantCircle(index, tag.label) }}
<q-tooltip :class="`bg-${tag.color}`" :delay="1000" :offset="[0, 10]"
>{{ tag.label }}
</q-tooltip>
</q-chip>
<q-btn-dropdown
v-if="tagsWantToggle && index == tagItems.length - 1 && wantDropdown"
flat
color="grey"
class="q-mx-none q-px-none"
label=""
dropdown-icon="expand_more"
size="8px"
>
<q-list
v-for="(tagDefault, index) in dropdownTagItems"
:key="index"
:class="`text-${tagDefault.color}`"
>
<q-item clickable v-close-popup @click="addTag(tagDefault)">
<q-item-section>
<q-item-label>{{ tagDefault.label }} </q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</div>
</template>
<script>
import { defineComponent, ref, onMounted } from "vue";
import { useCounterStore } from "../../stores/database";
export default defineComponent({
name: "list-tags",
props: {
tagsWantToggle: {
type: Boolean,
default: false,
},
tagItems: {},
},
setup(props) {
const store = useCounterStore();
var wantDropdown = ref(props.tagsWantToggle);
const visibleTagItems = ref(props.tagItems);
const storeTagItems = store.tags;
var dropdownTagItems = null;
if (store.selectedEmail.tagItems) {
dropdownTagItems = storeTagItems.filter(
({ label: id1 }) =>
!store.selectedEmail.tagItems.some(({ label: id2 }) => id2 === id1)
);
}
return {
dropdownTagItems,
storeTagItems,
wantDropdown,
visibleTagItems,
};
},
methods: {
...
},
});
</script>
<style></style>
The parent component called Content
contains this code:
<ListTags class="row" :tagsWantToggle="true" :tagItems="store.selectedEmail.tagItems" />
When the page is loaded, the selectedEmail
is just an empty object. Later, as I open one email the selected email will be filled with some data, but the ListTags
will not be visible. It is strange, because in the Content
file I'm using this code: <div class="text-subtitle2">{{ store.selectedEmail.message }}</div>
and it works perfectly. It updates if another email is selected.
How could I update the tags inside ListTag
?
This code inside setup
function:
const storeTagItems = store.tags;
var dropdownTagItems = null;
if (store.selectedEmail.tagItems) {
dropdownTagItems = storeTagItems.filter(
({ label: id1 }) =>
!store.selectedEmail.tagItems.some(({ label: id2 }) => id2 === id1)
);
}
... only runs once: when the setup()
function is run. It doesn't run every time store.selectedEmail.tagItems
or store.tags
change. In other words, it's not "reactive".
To make it reactive, write a computed:
const dropdownTagItems = computed(() =>
store.tags.filter(
({ label }) =>
!store.selectedEmail.tagItems.some(({ label: l }) => label === l)
)
);
store.tags
is nullable, replace store.tags.filter
with (store.tags || []).filter
.store.selectedEmail
or its tagItems
are nullable, replace store.selectedEmail.tagItems.some
with(store.selectedEmail?.tagItems || []).some
dropdownTagItems
will now react to any change in store.tags
or store.selectedEmail.tagItems
. It's reactive.
Notes:
dropdownTagItems
in more than one place, consider writing a store getter and move the above computed in that getter, so you don't need to repeat yourself in multiple components.storeTagItems
anywhere else in the component, you should make it reactive with any of these methods:store.tags
directly;computed
: const storeTagItems = computed(() => store.tags)
storeToRefs
helper importable from pinia
:const { tags: storeTagItems } = storeToRefs(store)