mirror of
https://github.com/nuxt/nuxt.git
synced 2024-12-11 14:57:15 +00:00
b2f573f685
Co-authored-by: Pooya Parsa <pooya@pi0.io>
343 lines
10 KiB
Markdown
343 lines
10 KiB
Markdown
# Data Fetching
|
||
|
||
Nuxt provides `useFetch`, `useLazyFetch`, `useAsyncData` and `useLazyAsyncData` to handle data fetching within your application.
|
||
|
||
::alert{icon=👉}
|
||
**`useFetch`, `useLazyFetch`, `useAsyncData` and `useLazyAsyncData` only work during `setup` or `Lifecycle Hooks`**
|
||
::
|
||
|
||
## `useFetch`
|
||
|
||
Within your pages, components and plugins you can use `useFetch` to universally fetch from any URL.
|
||
|
||
This composable provides a convenient wrapper around `useAsyncData` and `$fetch`. It automatically generates a key based on URL and fetch options, provides type hints for request url based on server routes, and infers API response type.
|
||
|
||
::ReadMore{link="/api/composables/use-fetch"}
|
||
::
|
||
|
||
### Example
|
||
|
||
```vue [app.vue]
|
||
<script setup>
|
||
const { data: count } = await useFetch('/api/count')
|
||
</script>
|
||
|
||
<template>
|
||
Page visits: {{ count }}
|
||
</template>
|
||
```
|
||
|
||
:LinkExample{link="/examples/composables/use-fetch"}
|
||
|
||
## `useLazyFetch`
|
||
|
||
This composable behaves identically to `useFetch` with the `lazy: true` option set. In other words, the async function does not block navigation. That means you will need to handle the situation where the data is `null` (or whatever value you have provided in a custom `default` factory function).
|
||
|
||
::ReadMore{link="/api/composables/use-lazy-fetch"}
|
||
::
|
||
|
||
### Example
|
||
|
||
```vue
|
||
<template>
|
||
<!-- you'll need to handle a loading state -->
|
||
<div v-if="pending">
|
||
Loading ...
|
||
</div>
|
||
<div v-else>
|
||
<div v-for="post in posts">
|
||
<!-- do something -->
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
const { pending, data: posts } = useLazyFetch('/api/posts')
|
||
watch(posts, (newPosts) => {
|
||
// Because posts starts out null, you won't have access
|
||
// to its contents immediately, but you can watch it.
|
||
})
|
||
</script>
|
||
```
|
||
|
||
## `useAsyncData`
|
||
|
||
Within your pages, components and plugins you can use `useAsyncData` to get access to data that resolves asynchronously.
|
||
|
||
::alert
|
||
You might be asking yourself: what is the difference between `useFetch` and `useAsyncData`?
|
||
|
||
In brief, `useFetch` receives a URL and gets that data, whereas `useAsyncData` 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.
|
||
::
|
||
|
||
::ReadMore{link="/api/composables/use-async-data"}
|
||
::
|
||
|
||
### Example
|
||
|
||
```ts [server/api/count.ts]
|
||
let counter = 0
|
||
export default () => {
|
||
counter++
|
||
return JSON.stringify(counter)
|
||
}
|
||
```
|
||
|
||
```vue [app.vue]
|
||
<script setup>
|
||
const { data } = await useAsyncData('count', () => $fetch('/api/count'))
|
||
</script>
|
||
|
||
<template>
|
||
Page visits: {{ data }}
|
||
</template>
|
||
```
|
||
|
||
:LinkExample{link="/examples/composables/use-async-data"}
|
||
|
||
## `useLazyAsyncData`
|
||
|
||
This composable behaves identically to `useAsyncData` with the `lazy: true` option set. In other words, the async function does not block navigation. That means you will need to handle the situation where the data is `null` (or whatever value you have provided in a custom `default` factory function).
|
||
|
||
::ReadMore{link="/api/composables/use-lazy-async-data"}
|
||
::
|
||
|
||
### Example
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
{{ pending ? 'Loading' : count }}
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
const { pending, data: count } = useLazyAsyncData('count', () => $fetch('/api/count'))
|
||
watch(count, (newCount) => {
|
||
// Because count starts out null, you won't have access
|
||
// to its contents immediately, but you can watch it.
|
||
})
|
||
</script>
|
||
```
|
||
|
||
## Refreshing Data
|
||
|
||
Sometimes throughout the course of your user's page visit, you may need to refresh the data loaded from the API. This can happen if the user chooses to paginate, filter results, search, etc.
|
||
|
||
You can make use of the `refresh()` method returned from the `useFetch()` composable to refresh the data with different query parameters:
|
||
|
||
```vue
|
||
<script setup>
|
||
const page = ref(1);
|
||
|
||
const { data: users, pending, refresh, error } = await useFetch(() => `users?page=${page.value}&take=6`, { baseURL: config.API_BASE_URL }
|
||
);
|
||
|
||
function previous() {
|
||
page.value--;
|
||
refresh();
|
||
}
|
||
|
||
function next() {
|
||
page.value++;
|
||
refresh();
|
||
}
|
||
</script>
|
||
```
|
||
|
||
The key to making this work is to call the `refresh()` method returned from the `useFetch()` composable when a query parameter has changed.
|
||
|
||
### `refreshNuxtData`
|
||
|
||
Invalidate the cache of `useAsyncData`, `useLazyAsyncData`, `useFetch` and `useLazyFetch` and trigger the refetch.
|
||
|
||
This method is useful if you want to refresh all the data fetching for a current page.
|
||
|
||
::ReadMore{link="/api/utils/refresh-nuxt-data"}
|
||
::
|
||
|
||
#### Example
|
||
|
||
```vue
|
||
<template>
|
||
<div>
|
||
{{ pending ? 'Loading' : count }}
|
||
</div>
|
||
<button @click="refresh">Refresh</button>
|
||
</template>
|
||
|
||
<script setup>
|
||
const { pending, data: count } = useLazyAsyncData('count', () => $fetch('/api/count'))
|
||
|
||
const refresh = () => refreshNuxtData('count')
|
||
</script>
|
||
```
|
||
|
||
### `clearNuxtData`
|
||
|
||
Delete cached data, error status and pending promises of `useAsyncData` and `useFetch`.
|
||
|
||
This method is useful if you want to invalidate the data fetching for another page.
|
||
|
||
::ReadMore{link="/api/utils/clear-nuxt-data"}
|
||
::
|
||
|
||
## Options API support
|
||
|
||
Nuxt 3 provides a way to perform `asyncData` fetching within the Options API. You must wrap your component definition within `defineNuxtComponent` for this to work.
|
||
|
||
```vue
|
||
<script>
|
||
export default defineNuxtComponent({
|
||
fetchKey: 'hello',
|
||
async asyncData () {
|
||
return {
|
||
hello: await $fetch('/api/hello')
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
::alert{type=warning}
|
||
Options API support for `asyncData` may well change before the stable release of Nuxt 3.
|
||
::
|
||
|
||
## Isomorphic `fetch` and `$fetch`
|
||
|
||
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.
|
||
|
||
::ReadMore{link="/api/utils/$fetch"}
|
||
::
|
||
|
||
### Example: Pass Client Headers to the API
|
||
|
||
We can use [`useRequestHeaders`](/api/composables/use-request-headers) to access and proxy cookies to the API from server-side.
|
||
|
||
The example below adds the request headers to an isomorphic `$fetch` call to ensure that the API endpoint has access to the same `cookie` header originally sent by the user.
|
||
|
||
```vue
|
||
<script setup>
|
||
const { data } = await useFetch('/api/me', {
|
||
headers: useRequestHeaders(['cookie'])
|
||
})
|
||
</script>
|
||
```
|
||
|
||
::alert{type="warning"}
|
||
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`
|
||
* `content-length`, `content-md5`, `content-type`
|
||
* `x-forwarded-host`, `x-forwarded-port`, `x-forwarded-proto`
|
||
* `cf-connecting-ip`, `cf-ray`
|
||
::
|
||
|
||
### Example: Pass Cookies From Server-side API Calls on SSR Response
|
||
|
||
If you want to pass on/proxy cookies in the other direction, from an internal request back to the client, you will need to handle this yourself.
|
||
|
||
```ts [composables/fetch.ts]
|
||
export const fetchWithCookie = async (url: string) => {
|
||
const res = await $fetch.raw(url)
|
||
const cookies = (res.headers.get('set-cookie') || '').split(',')
|
||
for (const cookie of cookies) {
|
||
appendHeader(useRequestEvent(), 'set-cookie', cookie)
|
||
}
|
||
return res._data
|
||
}
|
||
```
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
// This composable will automatically pass cookies to the client
|
||
const result = await fetchWithCookie('/api/with-cookie')
|
||
onMounted(() => console.log(document.cookie))
|
||
</script>
|
||
```
|
||
|
||
## Best Practices
|
||
|
||
The data returned by these composables will be stored inside the page payload. This means that every key returned that is not used in your component will be added to the payload.
|
||
|
||
::alert{icon=👉}
|
||
**We strongly recommend you only select the keys that you will use in your component.**
|
||
::
|
||
|
||
Imagine that `/api/mountains/everest` returns the following object:
|
||
|
||
```json
|
||
{
|
||
"title": "Mount Everest",
|
||
"description": "Mount Everest is Earth's highest mountain above sea level, located in the Mahalangur Himal sub-range of the Himalayas. The China–Nepal border runs across its summit point",
|
||
"height": "8,848 m",
|
||
"countries": [
|
||
"China",
|
||
"Nepal"
|
||
],
|
||
"continent": "Asia",
|
||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/Everest_kalapatthar.jpg/600px-Everest_kalapatthar.jpg"
|
||
}
|
||
```
|
||
|
||
If you plan to only use `title` and `description` in your component, you can select the keys by chaining the result of `$fetch` or `pick` option:
|
||
|
||
```vue
|
||
<script setup>
|
||
const { data: mountain } = await useFetch('/api/mountains/everest', { pick: ['title', 'description'] })
|
||
</script>
|
||
|
||
<template>
|
||
<h1>{{ mountain.title }}</h1>
|
||
<p>{{ mountain.description }}</p>
|
||
</template>
|
||
```
|
||
|
||
## Using Async Setup
|
||
|
||
If you are using `async setup()`, the current component instance will be lost after the first `await`. (This is a Vue 3 limitation.) If you want to use multiple async operations, such as multiple calls to `useFetch`, you will need to use `<script setup>` or await them together at the end of setup.
|
||
|
||
::alert{icon=👉}
|
||
Using `<script setup>` is recommended, as it removes the limitation of using top-level await. [Read more](https://vuejs.org/api/sfc-script-setup.html#top-level-await)
|
||
::
|
||
|
||
```vue
|
||
<script>
|
||
export default defineComponent({
|
||
async setup() {
|
||
const [{ data: organization }, { data: repos }] = await Promise.all([
|
||
useFetch(`https://api.github.com/orgs/nuxt`),
|
||
useFetch(`https://api.github.com/orgs/nuxt/repos`)
|
||
])
|
||
|
||
return {
|
||
organization,
|
||
repos
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<header>
|
||
<h1>{{ organization.login }}</h1>
|
||
<p>{{ organization.description }}</p>
|
||
</header>
|
||
</template>
|
||
```
|
||
|
||
## Directly Calling an API Endpoint
|
||
|
||
There are instances where you may need to directly call the API. Nuxt 3 provides a globally available `$fetch` method using [unjs/ohmyfetch](https://github.com/unjs/ohmyfetch) (in addition to `fetch`)
|
||
with the same API as the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch).
|
||
|
||
Using `$fetch` has a number of benefits, including:
|
||
|
||
It will handle 'smartly' making direct API calls if it's running on the server, or making a client-side call to your API if it's running on the client. (It can also handle calling third-party APIs.)
|
||
|
||
Plus, it comes with convenience features including automatically parsing responses and stringifying data.
|