From 6f7d86be787eea78dba12ad00caa7af9891a93b9 Mon Sep 17 00:00:00 2001 From: Jongmin Yoon Date: Thu, 24 Aug 2023 21:06:29 +0900 Subject: [PATCH] fix(nuxt): recreate `asyncData` when `immediate` is disabled (#20980) --- docs/3.api/1.composables/use-async-data.md | 8 +++---- docs/3.api/1.composables/use-fetch.md | 19 ++++++++-------- .../nuxt/src/app/composables/asyncData.ts | 2 +- test/basic.test.ts | 20 +++++++++++++++++ test/fixtures/basic/pages/index.vue | 3 +++ .../immediate-remove-unmounted.vue | 22 +++++++++++++++++++ 6 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 test/fixtures/basic/pages/useAsyncData/immediate-remove-unmounted.vue diff --git a/docs/3.api/1.composables/use-async-data.md b/docs/3.api/1.composables/use-async-data.md index 6577e5c19c..841f5d6085 100644 --- a/docs/3.api/1.composables/use-async-data.md +++ b/docs/3.api/1.composables/use-async-data.md @@ -25,11 +25,11 @@ function useAsyncData( type AsyncDataOptions = { server?: boolean lazy?: boolean + immediate?: boolean default?: () => DataT | Ref | null transform?: (input: DataT) => DataT pick?: string[] watch?: WatchSource[] - immediate?: boolean } type AsyncData = { @@ -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. * **handler**: an asynchronous function that returns a value * **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`) + * _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 * _pick_: only pick specified keys in this array from the `handler` function result * _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 `` 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. diff --git a/docs/3.api/1.composables/use-fetch.md b/docs/3.api/1.composables/use-fetch.md index f25564539c..b3bdc3cd11 100644 --- a/docs/3.api/1.composables/use-fetch.md +++ b/docs/3.api/1.composables/use-fetch.md @@ -29,7 +29,7 @@ type UseFetchOptions = { default?: () => DataT transform?: (input: DataT) => DataT pick?: string[] - watch?: WatchSource[] + watch?: WatchSource[] | false } type AsyncData = { @@ -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. :: -* **Options (from [`useAsyncData`](/docs/api/composables/use-async-data) )**: - * `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. - * `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. - * `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`. - * `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`) +* **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` is used. + * `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 + * `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} 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`. diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts index b086e705bc..cd50a3c549 100644 --- a/packages/nuxt/src/app/composables/asyncData.ts +++ b/packages/nuxt/src/app/composables/asyncData.ts @@ -145,7 +145,7 @@ export function useAsyncData< const hasCachedData = () => getCachedData() !== undefined // Create or use a shared asyncData entity - if (!nuxt._asyncData[key]) { + if (!nuxt._asyncData[key] || !options.immediate) { nuxt._asyncData[key] = { data: ref(getCachedData() ?? options.default!()), pending: ref(!hasCachedData()), diff --git a/test/basic.test.ts b/test/basic.test.ts index b6748654f5..9e8e7d40ff 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -1848,6 +1848,26 @@ describe.skipIf(isWindows)('useAsyncData', () => { await page.locator('#status5-values').getByText('idle,pending,success').waitFor() 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', () => { diff --git a/test/fixtures/basic/pages/index.vue b/test/fixtures/basic/pages/index.vue index eda06465a9..3d7d5fe2f0 100644 --- a/test/fixtures/basic/pages/index.vue +++ b/test/fixtures/basic/pages/index.vue @@ -26,6 +26,9 @@ islands + + Immediate remove unmounted + Chunk error diff --git a/test/fixtures/basic/pages/useAsyncData/immediate-remove-unmounted.vue b/test/fixtures/basic/pages/useAsyncData/immediate-remove-unmounted.vue new file mode 100644 index 0000000000..eded79b477 --- /dev/null +++ b/test/fixtures/basic/pages/useAsyncData/immediate-remove-unmounted.vue @@ -0,0 +1,22 @@ + + +