vue.jsionic-frameworkvuejs3ionic5vue-suspense

Vue Suspense in Ionic view


I have this view:

<template>
  <ion-page>
    <ion-router-outlet></ion-router-outlet>
    <suspense>
      <template #default>
        {{ pages }}
      </template>
      <template #fallback>
        <div>Loading...</div>
      </template> 
    </suspense>
  </ion-page>
</template>
...
async setup() {
    const storageRef = storage.ref();
    const bookRef = storageRef.child("content/books/bk0000001");
    const pages = await bookRef.listAll();
    console.log({ pages }); // is logged correctly

    return { pages }
}

Although it seems to load the content fine but the template is not rendered, the page remains empty neither the default not the fallback content is displayed.

What am I doing wrong?


Solution

  • When a component's setup() is async, the component has to be within a <suspense> in the parent of the component (not in the component itself).

    You should move the {{ pages }} markup and the data fetching from the async setup() into its own component:

    <!-- AsyncPages.vue -->
    <template>
      <ul>
        <li v-for="page in pages" :key="page.id">{{ page.title }}</li>
      </ul>
    </template>
    
    <script>
    import { defineComponent } from 'vue'
    
    export default defineComponent({
      async setup() {
        const resp = await fetch('https://jsonplaceholder.typicode.com/posts')
        const pages = await resp.json()
        return { pages }
      }
    })
    </script>
    

    And update the parent to use the new component in a <suspense>:

    <!-- Parent.vue -->
    <template>
      <ion-page>
        <ion-router-outlet></ion-router-outlet>
        <suspense>
          <template #default>
            <AsyncPages />
          </template>
          <template #fallback>
            <div>Loading...</div>
          </template>
        </suspense>
      </ion-page>
    </template>
    
    <script>
    import { defineComponent } from 'vue'
    import AsyncPages from '@/components/AsyncPages.vue'
    
    export default defineComponent({
      components: {
        AsyncPages
      }
    })
    </script>
    

    demo 1

    Alternatively, you could rewrite the above into a non-async setup(), using an onMounted async hook instead:

    <!-- Parent.vue -->
    <template>
      <ion-page>
        <ion-router-outlet></ion-router-outlet>
        <ul v-if="pages">
          <li v-for="page in pages" :key="page.id">{{ page.title }}</li>
        </ul>
        <div v-else>Loading...</div>
      </ion-page>
    </template>
    
    <script>
    import { defineComponent, onMounted, ref } from 'vue'
    
    export default defineComponent({
      setup() {
        const pages = ref(null)
    
        onMounted(async () => {
          const resp = await fetch('https://jsonplaceholder.typicode.com/posts')
          pages.value = await resp.json()
        })
    
        return { pages }
      }
    })
    </script>
    

    demo 2