docs: improvements on data-fetching

This commit is contained in:
Sébastien Chopin 2023-12-18 11:20:04 +01:00
parent d5c95ad472
commit 6d50b4744b
2 changed files with 80 additions and 52 deletions

View File

@ -24,9 +24,9 @@ When using a framework like Nuxt that can perform calls and render pages on both
The [`useFetch`](/docs/api/composables/use-fetch) and [`useAsyncData`](/docs/api/composables/use-async-data) composables ensure that once an API call is made on the server, the data is properly forwarded to the client in the payload.
The payload is a JavaScript object accessible through [`useNuxtApp().payload`](/docs/api/composables/use-nuxt-app#payload). It is used on the client to avoid refetching the same data when the code is executed in the browser.
The payload is a JavaScript object accessible through [`useNuxtApp().payload`](/docs/api/composables/use-nuxt-app#payload). It is used on the client to avoid refetching the same data when the code is executed in the browser [during hydration](/docs/guide/concepts/rendering#universal-rendering).
::callout
::callout{color="blue" icon="i-ph-info-duotone"}
Use the [Nuxt DevTools](https://devtools.nuxt.com) to inspect this data in the **Payload tab**.
::
@ -34,6 +34,10 @@ Use the [Nuxt DevTools](https://devtools.nuxt.com) to inspect this data in the *
Nuxt uses Vues [`<Suspense>`](https://vuejs.org/guide/built-ins/suspense) component under the hood to prevent navigation before every async data is available to the view. The data fetching composables can help you leverage this feature and use what suits best on a per-calls basis.
::callout{color="blue" icon="i-ph-info-duotone"}
You can add the [`<NuxtLoadingIndicator>`](/docs/api/components/nuxt-loading-indicator) to add a progress bar between page navigations.
::
## `useFetch`
The [`useFetch`](/docs/api/composables/use-fetch) composable is the most straightforward way to perform data fetching.
@ -44,7 +48,7 @@ const { data: count } = await useFetch('/api/count')
</script>
<template>
Page visits: {{ count }}
<p>Page visits: {{ count }}</p>
</template>
```
@ -58,69 +62,85 @@ This composable is a wrapper around the [`useAsyncData`](/docs/api/composables/u
Nuxt includes the `ofetch` library, and is auto-imported as the `$fetch` alias globally across your application. It's what `useFetch` uses behind the scenes.
```ts
const users = await $fetch('/api/users').catch((error) => error.data)
```vue [pages/todos.vue]
<script setup>
async function addTodo() {
const todo = await $fetch('/api/todos', {
method: 'POST',
body: {
// My todo data
}
})
}
</script>
```
::callout
Beware that using only `$fetch` will not provide [network calls de-duplication and navigation prevention](#why-using-specific-composables). It is recommended to use `$fetch` when posting data to an event handler, when doing client-side only logic, or combined with `useAsyncData`.
::
The `ofetch` library is built on top of [the `fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) and adds handy features to it:
- Works the same way in browser, Node or worker environments
- Automatic response parsing
- Error handling
- Auto-retry
- Interceptors
::read-more{title="ofetch" to="https://github.com/unjs/ofetch" target="_blank"}
Read the full documentation of `ofetch`
::callout{color="amber" icon="i-ph-warning-duotone"}
Beware that using only `$fetch` will not provide [network calls de-duplication and navigation prevention](#why-using-specific-composables). :br
It is recommended to use `$fetch` for client-side interactions (event based) or combined with [`useAsyncData`](#useasyncdata) when fetching the initial component data.
::
::read-more{to="/docs/api/utils/dollarfetch"}
Read more about `$fetch`
Read more about `$fetch`.
::
## `useAsyncData`
The `useAsyncData` composable is responsible for wrapping async logic and returning the result once it is resolved.
Indeed, `useFetch(url)` is nearly equivalent to `useAsyncData(url, () => $fetch(url))` - it's developer experience sugar for the most common use case.
::callout
`useFetch(url)` is nearly equivalent to `useAsyncData(url, () => $fetch(url))` :br
Iit's developer experience sugar for the most common use case.
::
There are some cases when using the [`useFetch`](/docs/api/composables/use-fetch) composable is not appropriate, for example when a CMS or a third-party provide their own query layer. In this case, you can use [`useAsyncData`](/docs/api/composables/use-async-data) to wrap your calls and still keep the benefits provided by the composable.
The first argument of [`useAsyncData`](/docs/api/composables/use-async-data) is the unique key used to cache the response of the second argument, the querying function. This argument can be ignored by directly passing the querying function. In that case, it will be auto-generated.
```ts
```vue [pages/users.vue]
<script setup>
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
// This is also possible:
const { data, error } = await useAsyncData(() => myGetFunction('users'))
</script>
```
Since the autogenerated key only takes into account the file and line where `useAsyncData` is invoked, it is recommended to always create your own key to avoid unwanted behavior, if you are creating your own custom composable that is wrapping `useAsyncData`.
::callout{color="blue" icon="i-ph-info-duotone"}
The first argument of [`useAsyncData`](/docs/api/composables/use-async-data) is a unique key used to cache the response of the second argument, the querying function. This key can be ignored by directly passing the querying function, the key will be auto-generated.
:br :br
Since the autogenerated key only takes into account the file and line where `useAsyncData` is invoked, it is recommended to always create your own key to avoid unwanted behavior, like when you are creating your own custom composable wrapping `useAsyncData`.
:br :br
Setting a key can be useful to share the same data between components using [`useNuxtData`](/docs/api/composables/use-nuxt-data) or to [refresh specific data](/docs/api/utils/refresh-nuxt-data#refresh-specific-data).
::
```ts
const id = ref(1)
```vue [pages/users/[id\\].vue]
<script setup>
const { id } = useRoute().params
const { data, error } = await useAsyncData(`user:${id.value}`, () => {
return myGetFunction('users', { id: id.value })
const { data, error } = await useAsyncData(`user:${id}`, () => {
return myGetFunction('users', { id })
})
</script>
```
The `useAsyncData` composable is a great way to wrap and wait for multiple `useFetch` to be done, and then retrieve the results of each.
```ts
```vue
<script setup>
const { data: discounts, pending } = await useAsyncData('cart-discount', async () => {
const [coupons, offers] = await Promise.all([$fetch('/cart/coupons'), $fetch('/cart/offers')])
const [coupons, offers] = await Promise.all([
$fetch('/cart/coupons'),
$fetch('/cart/offers')
])
return {
coupons,
offers
}
return { coupons, offers }
})
// data.value.coupons
// data.value.offers
</script>
```
::read-more{to="/docs/api/composables/use-async-data"}
Read more about `useAsyncData`
Read more about `useAsyncData`.
::
## Options
@ -153,16 +173,18 @@ const { pending, data: posts } = useFetch('/api/posts', {
You can alternatively use [`useLazyFetch`](/docs/api/composables/use-lazy-fetch) and `useLazyAsyncData` as convenient methods to perform the same.
```ts
```vue
<script setup lang="ts">
const { pending, data: posts } = useLazyFetch('/api/posts')
</script>
```
::read-more{to="/docs/api/composables/use-lazy-fetch"}
Read more about `useLazyFetch`
Read more about `useLazyFetch`.
::
::read-more{to="/docs/api/composables/use-lazy-async-data"}
Read more about `useLazyAsyncData`
Read more about `useLazyAsyncData`.
::
### Client-only fetching
@ -191,7 +213,9 @@ The `pick` option helps you to minimize the payload size stored in your HTML doc
```vue
<script setup lang="ts">
/* only pick the fields used in your template */
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
const { data: mountain } = await useFetch('/api/mountains/everest', {
pick: ['title', 'description']
})
</script>
<template>
@ -246,7 +270,7 @@ const { data, error, execute, refresh } = await useFetch('/api/users')
The `execute` function is an alias for `refresh` that works in exactly the same way but is more semantic for cases when the fetch is [not immediate](#not-immediate).
::callout
::callout{color="blue" icon="i-ph-info-duotone"}
To globally refetch or invalidate cached data, see [`clearNuxtData`](/docs/api/utils/clear-nuxt-data) and [`refreshNuxtData`](/docs/api/utils/refresh-nuxt-data).
::
@ -254,23 +278,27 @@ To globally refetch or invalidate cached data, see [`clearNuxtData`](/docs/api/u
To re-run your fetching function each time other reactive values in your application change, use the `watch` option. You can use it for one or multiple _watchable_ elements.
```ts
```vue
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch('/api/users', {
/* Changing the id will trigger a refetch */
watch: [id]
})
</script>
```
Note that **watching a reactive value won't change the URL fetched**. For example, this will keep fetching the same initial ID of the user because the URL is constructed at the moment the function is invoked.
```ts
```vue
<script setup lang="ts">
const id = ref(1)
const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
watch: [id]
})
</script>
```
If you need to change the URL based on a reactive value, you may want to use a [computed URL](#computed-url) instead.
@ -279,7 +307,8 @@ If you need to change the URL based on a reactive value, you may want to use a [
Sometimes you may need to compute an URL from reactive values, and refresh the data each time these change. Instead of juggling your way around, you can attach each param as a reactive value. Nuxt will automatically use the reactive value and re-fetch each time it changes.
```ts
```vue
<script setup lang="ts">
const id = ref(null)
const { data, pending } = useLazyFetch('/api/user', {
@ -287,6 +316,7 @@ const { data, pending } = useLazyFetch('/api/user', {
user_id: id
}
})
</script>
```
In the case of more complex URL construction, you may use a callback as a [computed getter](https://vuejs.org/guide/essentials/computed.html) that returns the URL string.
@ -375,7 +405,7 @@ const { data } = await useFetch('/api/me', { headers })
</script>
```
::callout
::callout{color="amber" icon="i-ph-warning-duotone"}
Be very careful before proxying headers to an external API and just include headers that you need. Not all headers are safe to be bypassed and might introduce unwanted behavior. Here is a list of common headers that are NOT to be proxied:
- `host`, `accept`
@ -410,7 +440,7 @@ export const fetchWithCookie = async (event: H3Event, url: string) => {
// This composable will automatically pass cookies to the client
const event = useRequestEvent()
const result = await fetchWithCookie(event, '/api/with-cookie')
const { data: result } = await useAsyncData(() => fetchWithCookie(event, '/api/with-cookie'))
onMounted(() => console.log(document.cookie))
</script>
@ -434,8 +464,8 @@ export default defineNuxtComponent({
</script>
```
::callout
Using `<script setup lang="ts">` is the recommended way of declaring Vue components in Nuxt 3.
::callout{color="blue" icon="i-ph-info-duotone"}
Using `<script setup>` or `<script setup lang="ts">` are the recommended way of declaring Vue components in Nuxt 3.
::
:read-more{to="/docs/api/utils/define-nuxt-component"}

View File

@ -14,11 +14,9 @@ Add `<NuxtLoadingIndicator/>` in your [`app.vue`](/docs/guide/directory-structur
```vue [app.vue]
<template>
<NuxtLoadingIndicator />
<NuxtLayout>
<div>
<NuxtLoadingIndicator /> <!-- here -->
<NuxtPage />
</div>
<NuxtPage />
</NuxtLayout>
</template>
```