fix(nuxt): recreate asyncData when immediate is disabled (#20980)

This commit is contained in:
Jongmin Yoon 2023-08-24 21:06:29 +09:00 committed by GitHub
parent a28ee6b014
commit 6f7d86be78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 60 additions and 14 deletions

View File

@ -25,11 +25,11 @@ function useAsyncData<DataT, DataE>(
type AsyncDataOptions<DataT> = { type AsyncDataOptions<DataT> = {
server?: boolean server?: boolean
lazy?: boolean lazy?: boolean
immediate?: boolean
default?: () => DataT | Ref<DataT> | null default?: () => DataT | Ref<DataT> | null
transform?: (input: DataT) => DataT transform?: (input: DataT) => DataT
pick?: string[] pick?: string[]
watch?: WatchSource[] watch?: WatchSource[]
immediate?: boolean
} }
type AsyncData<DataT, ErrorT> = { type AsyncData<DataT, ErrorT> = {
@ -53,13 +53,13 @@ type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
* **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests. If you do not provide a key, then a key that is unique to the file name and line number of the instance of [`useAsyncData`](/docs/api/composables/use-async-data) will be generated for you. * **key**: a unique key to ensure that data fetching can be properly de-duplicated across requests. If you do not provide a key, then a key that is unique to the file name and line number of the instance of [`useAsyncData`](/docs/api/composables/use-async-data) will be generated for you.
* **handler**: an asynchronous function that returns a value * **handler**: an asynchronous function that returns a value
* **options**: * **options**:
* _lazy_: whether to resolve the async function after loading the route, instead of blocking client-side navigation (defaults to `false`)
* _default_: a factory function to set the default value of the data, before the async function resolves - particularly useful with the `lazy: true` option
* _server_: whether to fetch the data on the server (defaults to `true`) * _server_: whether to fetch the data on the server (defaults to `true`)
* _lazy_: whether to resolve the async function after loading the route, instead of blocking client-side navigation (defaults to `false`)
* _immediate_: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
* _default_: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
* _transform_: a function that can be used to alter `handler` function result after resolving * _transform_: a function that can be used to alter `handler` function result after resolving
* _pick_: only pick specified keys in this array from the `handler` function result * _pick_: only pick specified keys in this array from the `handler` function result
* _watch_: watch reactive sources to auto-refresh * _watch_: watch reactive sources to auto-refresh
* _immediate_: When set to `false`, will prevent the request from firing immediately. (defaults to `true`)
Under the hood, `lazy: false` uses `<Suspense>` to block the loading of the route before the data has been fetched. Consider using `lazy: true` and implementing a loading state instead for a snappier user experience. Under the hood, `lazy: false` uses `<Suspense>` to block the loading of the route before the data has been fetched. Consider using `lazy: true` and implementing a loading state instead for a snappier user experience.

View File

@ -29,7 +29,7 @@ type UseFetchOptions<DataT> = {
default?: () => DataT default?: () => DataT
transform?: (input: DataT) => DataT transform?: (input: DataT) => DataT
pick?: string[] pick?: string[]
watch?: WatchSource[] watch?: WatchSource[] | false
} }
type AsyncData<DataT, ErrorT> = { type AsyncData<DataT, ErrorT> = {
@ -63,14 +63,15 @@ type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
All fetch options can be given a `computed` or `ref` value. These will be watched and new requests made automatically with any new values if they are updated. All fetch options can be given a `computed` or `ref` value. These will be watched and new requests made automatically with any new values if they are updated.
:: ::
* **Options (from [`useAsyncData`](/docs/api/composables/use-async-data) )**: * **Options (from `useAsyncData`)**:
* `key`: a unique key to ensure that data fetching can be properly de-duplicated across requests, if not provided, it will be generated based on the static code location where [`useAsyncData`](/docs/api/composables/use-async-data) is used. * `key`: a unique key to ensure that data fetching can be properly de-duplicated across requests, if not provided, it will be generated based on the static code location where `useAsyncData` is used.
* `server`: Whether to fetch the data on the server (defaults to `true`). * `server`: whether to fetch the data on the server (defaults to `true`)
* `default`: A factory function to set the default value of the data, before the async function resolves - particularly useful with the `lazy: true` option. * `lazy`: whether to resolve the async function after loading the route, instead of blocking client-side navigation (defaults to `false`)
* `pick`: Only pick specified keys in this array from the `handler` function result. * `immediate`: when set to `false`, will prevent the request from firing immediately. (defaults to `true`)
* `watch`: Watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`. * `default`: a factory function to set the default value of the `data`, before the async function resolves - useful with the `lazy: true` or `immediate: false` option
* `transform`: A function that can be used to alter `handler` function result after resolving. * `transform`: a function that can be used to alter `handler` function result after resolving
* `immediate`: When set to `false`, will prevent the request from firing immediately. (defaults to `true`) * `pick`: only pick specified keys in this array from the `handler` function result
* `watch`: watch an array of reactive sources and auto-refresh the fetch result when they change. Fetch options and URL are watched by default. You can completely ignore reactive sources by using `watch: false`. Together with `immediate: false`, this allows for a fully-manual `useFetch`.
::alert{type=warning} ::alert{type=warning}
If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the [`useFetch`](/docs/api/composables/use-fetch) call will not match other [`useFetch`](/docs/api/composables/use-fetch) calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`. If you provide a function or ref as the `url` parameter, or if you provide functions as arguments to the `options` parameter, then the [`useFetch`](/docs/api/composables/use-fetch) call will not match other [`useFetch`](/docs/api/composables/use-fetch) calls elsewhere in your codebase, even if the options seem to be identical. If you wish to force a match, you may provide your own key in `options`.

View File

@ -145,7 +145,7 @@ export function useAsyncData<
const hasCachedData = () => getCachedData() !== undefined const hasCachedData = () => getCachedData() !== undefined
// Create or use a shared asyncData entity // Create or use a shared asyncData entity
if (!nuxt._asyncData[key]) { if (!nuxt._asyncData[key] || !options.immediate) {
nuxt._asyncData[key] = { nuxt._asyncData[key] = {
data: ref(getCachedData() ?? options.default!()), data: ref(getCachedData() ?? options.default!()),
pending: ref(!hasCachedData()), pending: ref(!hasCachedData()),

View File

@ -1848,6 +1848,26 @@ describe.skipIf(isWindows)('useAsyncData', () => {
await page.locator('#status5-values').getByText('idle,pending,success').waitFor() await page.locator('#status5-values').getByText('idle,pending,success').waitFor()
await page.close() await page.close()
}) })
it('data is null after navigation when immediate false', async () => {
const page = await createPage('/useAsyncData/immediate-remove-unmounted')
await page.waitForLoadState('networkidle')
await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/useAsyncData/immediate-remove-unmounted')
expect(await page.locator('#immediate-data').getByText('null').textContent()).toBe('null')
await page.click('#execute-btn')
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain('null')
await page.click('#to-index')
await page.click('#to-immediate-remove-unmounted')
expect(await page.locator('#immediate-data').getByText('null').textContent()).toBe('null')
await page.click('#execute-btn')
expect(await page.locator('#immediate-data').getByText(',').textContent()).not.toContain('null')
await page.close()
})
}) })
describe.runIf(isDev())('component testing', () => { describe.runIf(isDev())('component testing', () => {

View File

@ -26,6 +26,9 @@
<NuxtLink id="islands" to="/islands"> <NuxtLink id="islands" to="/islands">
islands islands
</NuxtLink> </NuxtLink>
<NuxtLink id="to-immediate-remove-unmounted" to="/useAsyncData/immediate-remove-unmounted">
Immediate remove unmounted
</NuxtLink>
<NuxtLink to="/chunk-error" :prefetch="false"> <NuxtLink to="/chunk-error" :prefetch="false">
Chunk error Chunk error
</NuxtLink> </NuxtLink>

View File

@ -0,0 +1,22 @@
<template>
<div>
<div>immediate-remove-unmounted.vue</div>
<div id="immediate-data">
{{ data === null ? "null" : data }}
</div>
<button id="execute-btn" @click="execute">
execute
</button>
<NuxtLink id="to-index" to="/">
index
</NuxtLink>
</div>
</template>
<script setup lang="ts">
const { data, execute } = await useAsyncData('immediateFalse', () => $fetch('/api/random'), { immediate: false })
if (data.value !== null) {
throw new Error('Initial data should be null: ' + data.value)
}
</script>