Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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

FieldWhat it holds
dataThe server response
status'idle', 'pending', 'success' or 'error'
errorAn error or undefined
refreshA function that repeats the request
clearA 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.

Thanks for reading!

Donate to the site — we work hard for you.