docs: improve data fetching section (#23420)

This commit is contained in:
Italo 2023-09-27 11:33:08 -03:00 committed by GitHub
parent d788577fc2
commit 6fcc979877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -5,25 +5,25 @@ description: Nuxt provides composables to handle data fetching within your appli
# Data fetching # Data fetching
Nuxt comes with two composables and a built-in library to perform data-fetching in browser or server environments: `useFetch`, [`useAsyncData`](/docs/api/composables/use-async-data) and `$fetch` . Nuxt comes with two composables and a built-in library to perform data-fetching in browser or server environments: `useFetch`, [`useAsyncData`](/docs/api/composables/use-async-data) and `$fetch` . In a nutshell:
Used together, they ensure cross-environment compatibility and efficient caching and avoid duplicate network calls. - [`useFetch`](/docs/api/composables/use-fetch) is the most straightforward way to handle data fetching in a component setup function.
- [`$fetch`](/docs/api/utils/dollarfetch) is great to make network requests based on user interaction.
- [`useAsyncData`](/docs/api/composables/use-async-data), combined with `$fetch`, offers more fine-grained control.
[`useFetch`](/docs/api/composables/use-fetch) is the most straightforward way to handle data fetching in a component setup function. Both `useFetch` and `useAsyncData` share a common set of options and patterns that we will detail in the last sections.
On the other hand, when wanting to make a network request based on user interaction, `$fetch` is almost always the right handler to go for. Before that, it's imperative to know why these composables exist in the first place.
If you need more fine-grained control, you can use [`useAsyncData`](/docs/api/composables/use-async-data) and `$fetch` independently.
The two composables share a common set of options and patterns that we will detail in the last sections.
## Why using specific composables? ## Why using specific composables?
When using a framework like Nuxt that can perform calls and render pages on both client and server environments, some challenges must be addressed. This is why Nuxt provides composables to wrap your queries. When using a framework like Nuxt that can perform calls and render pages on both client and server environments, some challenges must be addressed. This is why Nuxt provides composables to wrap your queries, instead of letting the developer rely on `$fetch` calls alone.
### Network calls duplication ### Network calls duplication
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. This JavaScript object is accessible through [`useNuxtApp().payload`](/docs/api/composables/use-nuxt-app#payload) and is used on the client to avoid refetching the same data when the code is executed in the browser. 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.
::alert{icon=⚙️} ::alert{icon=⚙️}
Use the [Nuxt DevTools](https://devtools.nuxt.com) to inspect this data in the payload tab. Use the [Nuxt DevTools](https://devtools.nuxt.com) to inspect this data in the payload tab.
@ -31,7 +31,7 @@ Use the [Nuxt DevTools](https://devtools.nuxt.com) to inspect this data in the p
### Suspense ### Suspense
Nuxt uses Vues `<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. 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.
::alert{icon=👉} ::alert{icon=👉}
These composables are auto-imported and can be used in `setup` functions or lifecycle hooks These composables are auto-imported and can be used in `setup` functions or lifecycle hooks
@ -39,7 +39,7 @@ These composables are auto-imported and can be used in `setup` functions or life
## `useFetch` ## `useFetch`
[`useFetch`](/docs/api/composables/use-fetch) is the most straightforward way to perform data fetching. It is a wrapper around the [`useAsyncData`](/docs/api/composables/use-async-data) composable and `$fetch` utility. The [`useFetch`](/docs/api/composables/use-fetch) composable is the most straightforward way to perform data fetching.
```vue [app.vue] ```vue [app.vue]
<script setup lang="ts"> <script setup lang="ts">
@ -51,6 +51,8 @@ const { data: count } = await useFetch('/api/count')
</template> </template>
``` ```
This composable is a wrapper around the [`useAsyncData`](/docs/api/composables/use-async-data) composable and `$fetch` utility.
::ReadMore{link="/docs/api/composables/use-fetch"} ::ReadMore{link="/docs/api/composables/use-fetch"}
:: ::
@ -59,7 +61,17 @@ const { data: count } = await useFetch('/api/count')
## `$fetch` ## `$fetch`
The `ofetch` library is built on top of the `fetch` API and adds handy features to it: 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)
```
::alert{type="warning"}
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 - Works the same way in browser, Node or worker environments
- Automatic response parsing - Automatic response parsing
@ -71,34 +83,45 @@ The `ofetch` library is built on top of the `fetch` API and adds handy features
[Read the full documentation of ofetch](https://github.com/unjs/ofetch) [Read the full documentation of ofetch](https://github.com/unjs/ofetch)
:: ::
ofetch is auto-imported by Nuxt and used by the [`useFetch`](/docs/api/composables/use-fetch) composable.
It can also be used in your whole application with the `$fetch` alias:
```ts
const users = await $fetch('/api/users').catch((error) => error.data)
```
::alert{type="warning"}
Beware that using only `$fetch` will not provide the benefits described in [the first section of this page](#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`.
::
::ReadMore{link="/docs/api/utils/dollarfetch"} ::ReadMore{link="/docs/api/utils/dollarfetch"}
:: ::
## `useAsyncData` ## `useAsyncData`
[`useFetch`](/docs/api/composables/use-fetch) receives a URL and gets that data, whereas [`useAsyncData`](/docs/api/composables/use-async-data) might have more complex logic. `useFetch(url)` is nearly equivalent to `useAsyncData(url, () => $fetch(url))` - it's developer experience sugar for the most common use case. The `useAsyncData` composable is responsible for wrapping async logic and returning the result once it is resolved.
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: Indeed, `useFetch(url)` is nearly equivalent to `useAsyncData(url, () => $fetch(url))` - it'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 ```ts
const { data, error } = await useAsyncData('users', () => myGetFunction('users')) const { data, error } = await useAsyncData('users', () => myGetFunction('users'))
``` ```
::alert{icon=👉} Since the autogenerated key only takes into account the file and line where `useAsyncData` is invoked, is recommended to always create your own key to avoid unwanted behavior, if you are creating your own custom composable that is wrapping `useAsyncData`.
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
const id = ref(1)
const { data, error } = await useAsyncData(`user:${id.value}`, () => {
return myGetFunction('users', { id: id.value })
})
```
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
const { data: discounts, pending } = await useAsyncData('cart-discount', async () => {
const [coupons, offers] = await Promise.all([$fetch('/cart/coupons'), $fetch('/cart/offers')])
return {
coupons,
offers
}
})
```
::ReadMore{link="/docs/api/composables/use-async-data"} ::ReadMore{link="/docs/api/composables/use-async-data"}
:: ::
@ -150,6 +173,9 @@ By default, data fetching composables will perform their asynchronous function o
Combined with the `lazy` option, this can be useful for data that is not needed on the first render (for example, non-SEO sensitive data). Combined with the `lazy` option, this can be useful for data that is not needed on the first render (for example, non-SEO sensitive data).
```ts ```ts
/* This call is performed before hydration */
const { article } = await useFetch('api/article')
/* This call will only be performed on the client */ /* This call will only be performed on the client */
const { pending, data: posts } = useFetch('/api/comments', { const { pending, data: posts } = useFetch('/api/comments', {
lazy: true, lazy: true,
@ -157,7 +183,7 @@ const { pending, data: posts } = useFetch('/api/comments', {
}) })
``` ```
The `useFetch` composable is meant to be invoked in setup method or called directly at the top level of a function in lifecycle hooks, otherwise you should use `$fetch` method. The `useFetch` composable is meant to be invoked in setup method or called directly at the top level of a function in lifecycle hooks, otherwise you should use [`$fetch` method](#fetch).
### Minimize payload size ### Minimize payload size
@ -185,6 +211,10 @@ const { data: mountains } = await useFetch('/api/mountains', {
}) })
``` ```
::alert{type="warning"}
Both `pick` and `transform` don't prevent the unwanted data from being fetched initially. But they will prevent unwanted data from being added to the payload transferred from server to client.
::
### Caching and refetching ### Caching and refetching
#### Keys #### Keys
@ -200,7 +230,7 @@ To get the cached data by key, you can use [`useNuxtData`](/docs/api/composables
#### Refresh and execute #### Refresh and execute
If you want to fetch or refresh data manually, use the `execute` or `refresh` function provided by the composables. (`execute` is an alias for `refresh` that works in exactly the same way but is more semantic for cases when `immediate: false`). If you want to fetch or refresh data manually, use the `execute` or `refresh` function provided by the composables.
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
@ -215,23 +245,58 @@ const { data, error, execute, refresh } = await useFetch('/api/users')
</template> </template>
``` ```
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).
::alert{icon=📘} ::alert{icon=📘}
To globally refetch or invalidate cached data, see [`clearNuxtData`](/docs/api/utils/clear-nuxt-data) and [`refreshNuxtData`](/docs/api/utils/refresh-nuxt-data). To globally refetch or invalidate cached data, see [`clearNuxtData`](/docs/api/utils/clear-nuxt-data) and [`refreshNuxtData`](/docs/api/utils/refresh-nuxt-data).
:: ::
#### Watch #### Watch
To re-run your fetching function each time other reactive values in your application change, use the `watch` option. 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 ```ts
const id = ref(1)
const { data, error, refresh } = await useFetch('/api/users', { const { data, error, refresh } = await useFetch('/api/users', {
/* Changing the id will trigger a refetch */ /* Changing the id will trigger a refetch */
watch: [id] watch: [id]
}) })
const id = ref(1)
``` ```
### Not immediate
The `useFetch` composable will start fetching data the moment is invoked. You may prevent this by setting `immediate: false`, for example, to wait for user interaction.
With that, you will need both the `status` to handle the fetch lifecycle, and `execute` to start the data fetch.
```vue
<script setup lang="ts">
const { data, error, execute, pending, status } = await useLazyFetch('/api/comments')
</script>
<template>
<div v-if="status === 'idle'">
<button @click="execute">Get data</button>
</div>
<div v-else-if="pending">
Loading comments...
</div>
<div v-else>
{{ data }}
</div>
</template>
```
For finer control, the `status` variable can be:
- `idle` when the fetch hasn't started
- `pending` when a fetch has started but not yet completed
- `error` when the fetch fails
- `success` when the fetch is completed successfully
## Passing Headers and cookies ## Passing Headers and cookies
When we call `$fetch` in the browser, user headers like `cookie` will be directly sent to the API. But during server-side-rendering, since the `$fetch` request takes place 'internally' within the server, it doesn't include the user's browser cookies, nor does it pass on cookies from the fetch response. When we call `$fetch` in the browser, user headers like `cookie` will be directly sent to the API. But during server-side-rendering, since the `$fetch` request takes place 'internally' within the server, it doesn't include the user's browser cookies, nor does it pass on cookies from the fetch response.
@ -245,6 +310,7 @@ The example below adds the request headers to an isomorphic `$fetch` call to ens
```vue ```vue
<script setup lang="ts"> <script setup lang="ts">
const headers = useRequestHeaders(['cookie']) const headers = useRequestHeaders(['cookie'])
const { data } = await useFetch('/api/me', { headers }) const { data } = await useFetch('/api/me', { headers })
</script> </script>
``` ```
@ -266,11 +332,15 @@ Be very careful before proxying headers to an external API and just include head
import { appendResponseHeader, H3Event } from 'h3' import { appendResponseHeader, H3Event } from 'h3'
export const fetchWithCookie = async (event: H3Event, url: string) => { export const fetchWithCookie = async (event: H3Event, url: string) => {
/* Get the response from the server endpoint */
const res = await $fetch.raw(url) const res = await $fetch.raw(url)
/* Get the cookies from the response */
const cookies = (res.headers.get('set-cookie') || '').split(',') const cookies = (res.headers.get('set-cookie') || '').split(',')
/* Attach each cookie to our incoming Request */
for (const cookie of cookies) { for (const cookie of cookies) {
appendResponseHeader(event, 'set-cookie', cookie) appendResponseHeader(event, 'set-cookie', cookie)
} }
/* Return the data of the response */
return res._data return res._data
} }
``` ```
@ -279,7 +349,9 @@ export const fetchWithCookie = async (event: H3Event, url: string) => {
<script setup lang="ts"> <script setup lang="ts">
// This composable will automatically pass cookies to the client // This composable will automatically pass cookies to the client
const event = useRequestEvent() const event = useRequestEvent()
const result = await fetchWithCookie(event, '/api/with-cookie') const result = await fetchWithCookie(event, '/api/with-cookie')
onMounted(() => console.log(document.cookie)) onMounted(() => console.log(document.cookie))
</script> </script>
``` ```