Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async function doesn't run upon page load

I'm using Nuxt v3.8.0 (which came with Vue v3.3.7). After implementing this answer, I have the following:

// .env
BASE_URL = https://www.my-website.com/wp/wp-json

// composables/customFetch.js
export const customFetch = (request, opts) => {
  const config = useRuntimeConfig()
  return useFetch(request, { baseURL: config.public.baseURL, ...opts })
}

// index.vue
<script setup>
import { customFetch } from '@/composables/customFetch'
const { data, pending, error, refresh } = await customFetch('/projects/v1/posts')
</script>

After page load, customFetch() never runs. But if I modify anything in the page's code such that Nuxt automatically updates the page, then customFetch() does load now.

What's going on? Why is it not working by itself upon page load?

like image 218
drake035 Avatar asked Sep 06 '25 03:09

drake035


2 Answers

The <script setup> block runs asynchronously, which means that data fetching code might not have been completed by the time the initial rendering is done. This can result in the data not being available immediately on page load.

To ensure that the data is available on page load, you can use the onMounted lifecycle hook. This ensures that the data fetching is completed before rendering the component:

<script setup>
import { customFetch } from '@/composables/customFetch'
import { onMounted } from 'vue'

const fetchData = async () => {
  const { data, pending, error, refresh } = await customFetch('/projects/v1/posts')
  // Assign data to your reactive variables
}

onMounted(() => {
  fetchData()
})
</script>
like image 179
Awais khan Avatar answered Sep 07 '25 21:09

Awais khan


The problem comes from the await call during setup, which turns the component into an async dependency.

There are two ways to deal with it:

  1. Rewrite the component
  2. Wrap it with Suspense

The first approach means that you initialize your refs with empty values and use .then() instead of await to fill them:

<script setup>
import { customFetch } from '@/composables/customFetch'
const data = ref([])
customFetch('/projects/v1/posts').then((response) => data.value = response.data)
</script>

Of course, you can throw all sorts of js at it to solve it differently, as long as you avoid the top-level await and declare your refs during setup phase:

<script setup>
import { customFetch } from '@/composables/customFetch'
const data = ref([]) // <---- define ref during setup
;(async() => { // <---- wrap async call in iife
  const { data, pending, error, refresh } = await customFetch('/projects/v1/posts')
  data.value = data
})()
</script>

Here is a simple playground


The second option uses Suspense, which can handle async dependencies directly. For this, you can leave your component as it is, but you have to wrap it in a suspense in the parent component.

So let's assume your component is <Index/>, than you can use it in a parent like this:

// IndexParent.vue
<template>
  <Suspense>
    <Index/>
  </Suspense>
</template>

or, if you want to show a loading indicator:

// IndexParent.vue
<template>
  <Suspense>
    <template #default>
      <Index/>
    </template>
    <template #fallback>
      loading...
    </template>
  </Suspense>
</template>

Note that Suspense is still an experimental feature.

Here is a snippet (easier to restart):

const { createApp, ref } = Vue;

const AsyncDependency = {
  template: `data: {{data}}`,
  async setup(){
    await new Promise(resolve => setTimeout(resolve, 2000))
    const data = ref([1,2,3])
    return {data}
  }
}

const App = { 
  components: {AsyncDependency},
  template: `
    <Suspense>
      <template #default>
        <AsyncDependency/>
      </template>
      <template #fallback>
       loading...
      </template>
    </Suspense>
  `,
}
const app = createApp(App)
app.mount('#app')
<div id="app"></div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

Note that in

const { data, pending, error, refresh } = await customFetch('/projects/v1/posts')

the resulting variables will not be reactive, which is usually not what you want in Vue. Instead, you probably want to put the data into refs:

const response = await customFetch('/projects/v1/posts')
const { data, pending, error, refresh } = toRefs(response)
like image 28
Moritz Ringler Avatar answered Sep 07 '25 19:09

Moritz Ringler