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?
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>
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:
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With