Home > Mobile >  Vue.js Mounted method is not triggering when I use computed method for filtering components
Vue.js Mounted method is not triggering when I use computed method for filtering components

Time:06-01

I need to display Image that I get from external api. I have ProductCard.vue where I fetch the image in mounted hook.

<template>
  <q-card
    
    flat
    bordered
  >
    <q-img
      :src="PRODUCT_IMG_SRC"
      sizes="(max-width: 400px) 400w,
              (min-width: 400px) and (max-width: 800px) 800w,
              (min-width: 800px) and (max-width: 1200px) 1200w,
              (min-width: 1200px) 1600w"
      loading="eager"
    />
    <q-card-section>
      <div >
        {{ truncate(product.description, 100) }}
      </div>
    </q-card-section>
  </q-card>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { truncate } from 'src/utils/textUtils';
import fallback_image from 'src/assets/dluhopis_houses_responsive.svg';

export default defineComponent({
  name: 'ProductCard',
  props: {
    product: Object,
    chosen: Boolean,
  },
  setup(props) {
    const PRODUCT_IMG_SRC = ref();

    onMounted(async() => {
      const IMG_URL = `https://ydc.datanasiti.cz/api/product/${props?.product?.externalId}/image`;
      const response = await fetch(IMG_URL);


      if (response.status === 200) {
        return (PRODUCT_IMG_SRC.value = IMG_URL);
      } else {
        PRODUCT_IMG_SRC.value = fallback_image;
      }
    });

    return {
      truncate,
      PRODUCT_IMG_SRC,
    };
  },
});
</script>

I load this component in parent component in v-for loop where I'm looping through computed property PageProducts which filters the products per page.


<template>
  <div
    v-if="pageProducts"
    :
    
  >
    <template v-if="!productChosen">
      <div
        v-for="(product, index) in pageProducts"
        :key="`md-${index}`"
        
      >
        <product-card
          :product="product"
          @product-detail="toggleDetail"
          @choose-product="chooseProduct"
        />
      </div>
    </template>
    <div
      
    >
      <q-pagination
        v-if="!productChosen"
        v-model="currentPage"
        :max="pagesCount()"
        direction-links
      />
    </div>
   
  </div>
</template>

<script lang="ts">
import { defineComponent, onMounted, reactive, ref, toRefs, computed } from 'vue';
import ProductCard from 'src/components/ProductCard.vue';
import { products } from 'src/api/api';
import { AxiosResponse } from 'axios';
import useDate from 'src/utils/useDate';
import { useQuasar } from 'quasar';
import { IProduct, PAYOUT_FREQUENCY } from 'src/interfaces/Purchase';



export default defineComponent({
  name: 'ProductCatalog',
  components: {
    ProductCard,
  },
  emits: ['chosen'],
  // eslint-disable-next-line no-unused-vars
  setup(props, { emit }) {
    const $q = useQuasar();
    const productsRef = ref([]);
    const currentPage = ref(1);
    const searchProduct = ref('');
    const productChosen = ref();
    const PAGE_SIZE = 8;
    const { formatDateDDMYYYY, yearsDiff } = useDate();
    const dialogState = reactive({
      expanded: false,
      product: {} as IProduct
    });


    onMounted(() => {
      products.getProducts().then((response: AxiosResponse) => {
        productsRef.value = response.data[0];
      });
    });

    const toggleDetail = (product: any) => {
      dialogState.product = product;
      dialogState.expanded = true;
    };

    const chooseProduct = (product: IProduct, isChosen: boolean) => {
      productChosen.value = product;
      if(product) {
        $q.notify({
          message: 'Produkt byl úspěšně vybrán.',
          icon: 'check_circle',
          color: 'positive',
        });
      }
      emit('chosen', isChosen, product);
      dialogState.expanded = false;
    };


    const pagesCount = (() => {
      if (productsRef.value.length > PAGE_SIZE) {
        return Math.ceil(productsRef.value.length / PAGE_SIZE);
      }
      return 1;
    });

    const pageProducts = computed(() => {
      const pageProducts = productsRef.value;
      if (pagesCount() > 1) {
        pageProducts = productsRef.value.slice(
          (currentPage.value * PAGE_SIZE) - PAGE_SIZE,
          currentPage.value * PAGE_SIZE
        );
      }
      if (searchProduct.value) {
        return productsRef.value.filter((product: any) => {
          return searchProduct.value
            .toLowerCase()
            .split(' ')
            .every((v: any) => product.name.toLowerCase().includes(v));
        });
      } else {
        return pageProducts;
      }
    });

    /* eslint-disable-next-line vue/script-indent */
    const payoutFrequency = computed(() => {
      const frequency = {
        1: PAYOUT_FREQUENCY.MONTHLY,
        3: PAYOUT_FREQUENCY.QUATERLY,
        6: PAYOUT_FREQUENCY.SEMI_ANNUAL,
        12: PAYOUT_FREQUENCY.ANNUAL
      } as any;

      return frequency[dialogState.product.frequency];
    });

    return {
      productsRef,
      pageProducts,
      pagesCount,
      searchProduct,
      currentPage,
      payoutFrequency,
      productChosen,
      chooseProduct,
      toggleDetail,
      formatDateDDMYYYY,
      yearsDiff,
      ...toRefs(dialogState)
    };
  },
});
</script>

The problem is that in child component ProductCard.vue the mounted method is not called for every product when the computed propety in parent component change. It's called only for first 8 products and then when I switch from 7th page to 4th. That's very strange.

Any hint why is this happening? Or do you know any approach how to make this better ?

CodePudding user response:

When Vue mounts a component, it only happens once as part of a component lifecycle. After that Vue's reactivity system simply updates the DOM as reactive data changes. So quite rightly your #mounted hook is only firing once.

CodePudding user response:

Following up to my previous post, if the products your are passing to your template in the interator (v-for) are actually changing (eg due to pagination) and you need to re-render the component entirely, you need to key the component to inform Vue that your reactive prop has changed at that the component should be re-rendered from scratch.

<product-card
  :product="product"
  @product-detail="toggleDetail"
  @choose-product="chooseProduct"
  :key="product.id"
/>
  • Related