Loading, errors and refreshing data
A request can take a while or fail, and data can go stale. Besides data, useFetch and useAsyncData return fields that describe the request state and a function to repeat it. With them a page can show "Loading…", an error message and a "Refresh" button.
What useFetch returns
| Field | What it holds |
|---|---|
data | The server response |
status | 'idle', 'pending', 'success' or 'error' |
error | An error or undefined |
refresh | A function that repeats the request |
clear | A function that resets the data and the error |
The pending field (true while the request runs) still exists, but status is more detailed.
An error
<!-- app/pages/posts/index.vue -->
<script setup lang="ts">
const { data: posts, error } = await useFetch('/api/posts')
</script>
<template>
<section>
<p v-if="error">Could not load posts: {{ error.message }}</p>
<PostCard v-for="post in posts" v-else :key="post.id" :post="post" />
</section>
</template>If the API returns an error, a message appears instead of the list. To check it, temporarily throw throw createError({ statusCode: 500 }) in server/api/posts.get.ts.
Lazy loading
With await, Nuxt does not show the page until the data arrives, so the pending state is hardly visible. The lazy: true option shows the page at once and loads the data afterwards:
<!-- app/pages/posts/index.vue -->
<script setup lang="ts">
const { data: posts, status } = await useFetch('/api/posts', {
lazy: true
})
</script>
<template>
<section>
<p v-if="status === 'pending'">Loading…</p>
<PostCard v-for="post in posts" v-else :key="post.id" :post="post" />
</section>
</template>When you follow a link to /posts, the page opens instantly with "Loading…", and a moment later the list appears. On the first load of the site the data still comes from the server inside the HTML. There is a shorthand too: useLazyFetch('/api/posts').
Refreshing with a button
<!-- app/pages/posts/index.vue -->
<script setup lang="ts">
const { data: posts, status, refresh } = await useFetch('/api/posts')
</script>
<template>
<section>
<button :disabled="status === 'pending'" @click="refresh()">Refresh</button>
<PostCard v-for="post in posts" :key="post.id" :post="post" />
</section>
</template>Add a post with the curl command from the request body lesson and press "Refresh": the new post appears without a page reload. The button is disabled while the request runs.
Refreshing from elsewhere
Every request has a key. If you set it explicitly, any component can refresh the data with refreshNuxtData:
<!-- app/pages/posts/index.vue -->
<script setup lang="ts">
const { data: posts } = await useFetch('/api/posts', { key: 'post-list' })
</script>Calling await refreshNuxtData('post-list') in another component loads this list again.
Common mistake
Turning on lazy: true and using the data right away in the script: posts.value.map(...). With lazy loading there is no data at first, posts.value is undefined, and the code crashes. With lazy requests always check the data, or set the option default: () => [] so that data holds an empty array until the response arrives.