Search code examples
vue.jsvuejs3quasar-frameworkquasarpinia

Component won't update in Vue


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?


Solution

  • 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)
      )
    );
    
    • if store.tags is nullable, replace store.tags.filter with (store.tags || []).filter.
    • if 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:

    • if you use 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.
    • if you use storeTagItems anywhere else in the component, you should make it reactive with any of these methods:
      a) use store.tags directly;
      b) make it a computed: const storeTagItems = computed(() => store.tags)
      c) use the storeToRefs helper importable from pinia:
      const { tags: storeTagItems } = storeToRefs(store)