Skip to content
Advertisement

Vue 3 component not updating after call from vue-router

I want to build a todo app with ionic-vue. It currently uses vue 3. I have this overview (called Lists.vue) where it is possible to click on multiple lists (where tasks should be loaded per list). However, everytime when I click on a list, the same data appears! It is as if the component is being reused but not re rendered/ updated.

I have tried all kinds of solutions. One of them was to apply a watch on the ref that is being changed, however, it does not matter, the end result stays the same. Also tried to give :key to router-link, still does not work.

My Lists.vue

  <ion-page>
    <ion-content v-if="chunk" class="flex flex-col overflow-auto ion-align-self-center content-wrapper">
        <ion-toolbar class="mt-2">
            <h1 class="text-4xl pl-5 font-semibold">Lijsten</h1> 
        </ion-toolbar>
        <div v-for="(categoryChunk, index) in chunk.value" :key="index"  class="flex flex-wrap w-full flex-row justify-around mt-2">
            <div v-for="category in categoryChunk" :key="category.id">
                <ion-card class='w-40 sm:w-80'>
                    <router-link :to="{ name: 'Index', params: {categoryId: category.id} }">
                        <ion-card-header class="flex">
                            <ion-icon class="mt-5 text-4xl" color="orange-secondary" :icon="allIcons[category.icon]"></ion-icon>
                            <div class="m-4"> 
                                <p><b>{{ category.title }}</b></p>
                                <p>Taken: {{ category.tasks }}</p>
                            </div>
                        </ion-card-header>
                        <ion-card-content><div class="line-vert"></div></ion-card-content>
                    </router-link>
                </ion-card>
            </div>
        </div>
    </ion-content>
     <div v-else>  
         <ion-spinner class="centered" color="orange" name="crescent"></ion-spinner>
    </div>
    <ion-fab vertical="bottom" horizontal="end" slot="fixed">
        <ion-fab-button color="orange-secondary" @click="setOpen(true)">
            <ion-icon class="text-4xl" color="light" :icon="allIcons.add"></ion-icon>
        </ion-fab-button>
    </ion-fab>
    <ion-modal
        :is-open="isOpenRef"
        css-class="my-custom-class"
    >
        <create-list v-on:on-close="setOpen(false)"></create-list>
  </ion-modal>
  </ion-page>
</template>

<script>
import { defineComponent, computed, ref, watch, onBeforeMount } from "vue";
import {
    IonPage, 
    IonCard, 
    IonCardHeader, 
    IonIcon, 
    IonCardContent,
    IonFab,
    IonFabButton,
    IonContent,
    IonToolbar,
    IonModal,
    IonSpinner,
    } from '@ionic/vue'

import  * as allIcons  from 'ionicons/icons'
import getCollection from "../../composables/getCollection"
import CreateList from './CreateList'

export default defineComponent ({
    components: {
        IonPage,
        IonCard,
        IonCardHeader, 
        IonIcon,
        IonCardContent,
        IonFab,
        IonFabButton,
        IonContent,
        IonToolbar,
        IonModal,
        CreateList,
        IonSpinner,
    },

    setup() {
        const { loadCollection } = getCollection();
        const chunk = ref()

        // Zet modal open/dicht
        const isOpenRef = ref(false);
        const setOpen = (state) => isOpenRef.value = state;
        
        // Laad alle categorieën uit de database 
        const reload = () => {
            loadCollection('categories').then(data => {
                chunk.value = computed(() => {
                    // Zet de items uit de database om in delen van twee.
                    const array = [];
                    const size = 2;
                    for(let i = 0; i < data.length; i += size) {
                        array.push(data.slice(i, i+size));
                    }
                    return array;
                })
            })
        }

        onBeforeMount(() => {
            reload();
        })

        watch(isOpenRef, () =>{
            reload()
        })
        
        return {
            allIcons,
            chunk,
            isOpenRef,
            setOpen,
        }
    }
})
</script>

My list called Index.vue (maybe I should just call it list.vue or something…)

<template>
    <ion-page>
        <ion-content v-if="category">
            <ion-toolbar>
                <div class="flex justify-between">
                    <h1 class="font-light pl-5">{{ category.title }}</h1>
                    <ion-icon class="text-2xl pr-5" :icon="closeOutline"  @click="redirectBack()"></ion-icon>
                </div>
            </ion-toolbar>
            {{ category }}
        </ion-content>
        <div v-else>  
            <ion-spinner class="centered" color="orange" name="crescent"></ion-spinner>
        </div>
    </ion-page>
</template>

<script>
import { defineComponent, ref } from "vue";
import  { closeOutline }  from 'ionicons/icons'

import { 
    IonPage,
    IonContent,
    IonToolbar,
    IonIcon,
    IonSpinner,
} from '@ionic/vue'
import { useRoute, useRouter } from "vue-router";
import getValue from "@/composables/getValue";

export default defineComponent ({
    components: {
        IonPage,
        IonContent,
        IonToolbar,
        IonIcon,
        IonSpinner
    },

    setup() {
        const router = useRouter()
        const route = useRoute()
        const { loadValue } = getValue()
        const category = ref()

        // redirect terug naar lists indien men op kruisje klikt.
        const redirectBack = () => {
            return router.push({name: 'Lists'})
        }
        
        // Ophalen van data van een lijst.
        loadValue('categories', route.params.categoryId).then(data => {
            category.value = data
        })

        return {
            closeOutline,
            redirectBack,
            category,
        }
    }
})
</script>

My composable function:

import {ref } from "@vue/reactivity";
import { todoFirestore } from "../firebase/config";

const getValue = () => {
    const error = ref(null);

    const loadValue = async (collectionName: string, id : string) => {
        try {
           let res = await todoFirestore.collection(collectionName).doc(id)
           .get();

           if (!res.exists) {
               throw Error('Lijst bestaat niet.');
           }

           return { ...res.data(), id: res.id }
        }
        catch (err) {
            error.value = err.message
        }
    }
    return { error , loadValue }
}

export default getValue;

If someone knows any possible solutions, or what I’m possibly doing wrong, please help! All solutions are very appreciated.

PS: Due to circumstances, I am currently not able to reply very fast, but I assure you that I will reply to your answers 🙂

Advertisement

Answer

Found the answer to my problem! I had to use watchEffect on the loadValue method in order to recall the data from the database. It would seem that Vue (after some research on the internet) wants to reuse components instead of rerendering them, which is more efficient.

The route params were being updated but the key of the component was not, however.

The setup function on Index.vue (the list of tasks)

 setup() {
        const router = useRouter()
        const route = useRoute()
        const { loadValue } = getValue()
        const category = ref()

        // redirect terug naar lists indien men op kruisje klikt.
        const redirectBack = () => {
            return router.push({name: 'Lists'})
        }
       
        // Ophalen van data van een lijst.
        const getCategory = () => {
            loadValue('categories', route.params.categoryId).then(data => {
                category.value = data
            })
        }

        watchEffect(() => {
            getCategory()
        })

        return {
            closeOutline,
            redirectBack,
            category,
        }
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement