Search code examples
typescriptvue.jsvuexnuxt.js

How to handle Vue component content before data has finished loading


I'm loading some product category data from Prismic into my Nuxt project, and I'm looking for some clarification regarding best practices. Specifically, how to handle the state in which the data is still being fetched, and there's no content to display in the sidebar.

Right now, here's what I've got working:

I have a sidebar component (Sidebar.vue):

<template>
    <div class="xl:flex-shrink-0 xl:w-64 border-r border-gray-200 pt-5 pb-4 bg-white overflow-y-auto ">
        <h3 class="text-xl font-bold text-gray-800">{{ navigation.heading }}</h3>
        <div class="mt-5 flex-grow flex flex-col">
            <nav class="flex-1 px-2 space-y-1 bg-white" aria-label="Sidebar">
            <div v-for="(category, index) in navigation.categories" :key="index">
                <SidebarItem v-if="!category.subcategories.length" :category="category"/>

                <SidebarSection v-else-if="category.subcategories.length" @click="toggleExpansion(index)" :category="category"/>
            </div>
            </nav>
        </div>
    </div>
</template>

<script lang="ts">
export default {
    data() {
        return {
            navigation: new Navigation('Categories')
        }
    },
    async mounted() {
        const that = this
        this.fetchCategories()
            .then(function() {
                that.navigation = that.$store.getters.navigation
            })
    },
    methods: {
        ...mapActions({ fetchCategories: 'fetchCategories' })
    }
}
</script>

As you can see, I have a navigation property which will contain all of the data required to populate the sidebar. At the moment, I have initialised a placeholder instance (new Navigation('Categories')), as without this, Vue reports that navigation is undefined.

This doesn't feel like an ideal way to go about this. What would be the most appropriate way to handle this intermediary state before the data is loaded, without providing a placeholder instance?


Solution

  • Nuxt's fetch hook could be useful here. It exposes $fetchState (includes pending, error, and timestamp) to use imperatively or in your template. Your template could check the pending flag before rendering the navigation data:

    <template>
        <div v-if="!$fetchState.pending" 👈
            class="xl:flex-shrink-0 xl:w-64 border-r border-gray-200 pt-5 pb-4 bg-white overflow-y-auto">
            <h3 class="text-xl font-bold text-gray-800">{{ navigation.heading }}</h3>
            <div class="mt-5 flex-grow flex flex-col">
                <nav class="flex-1 px-2 space-y-1 bg-white" aria-label="Sidebar">
                <div v-for="(category, index) in navigation.categories" :key="index">
                    <SidebarItem v-if="!category.subcategories.length" :category="category"/>
                    <SidebarSection v-else-if="category.subcategories.length" @click="toggleExpansion(index)" :category="category"/>
                </div>
                </nav>
            </div>
        </div>
    </template>
    
    <script lang="ts">
    export default {
        // BEFORE:
        // async mounted() {
        //     const that = this
        //     this.fetchCategories()
        //     .then(function() {
        //         that.navigation = that.$store.getters.navigation
        //     })
        // },
    
        // AFTER:
        async fetch() {
            await this.fetchCategories()
            this.navigation = this.$store.getters.navigation
        },
    }
    </script>