8000 fix(nuxt): respect cachedData with multiple asyncData calls (#32099) · nuxt/nuxt@261079c · GitHub
[go: up one dir, main page]

Skip to content

Commit 261079c

Browse files
authored
fix(nuxt): respect cachedData with multiple asyncData calls (#32099)
1 parent 5c6bbb4 commit 261079c

File tree

2 files changed

+53
-11
lines changed

2 files changed

+53
-11
lines changed

packages/nuxt/src/app/composables/asyncData.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ export interface AsyncDataExecuteOptions {
109109
dedupe?: 'cancel' | 'defer'
110110

111111
cause?: AsyncDataRefreshCause
112+
113+
/** @internal */
114+
cachedData?: any
112115
}
113116

114117
export interface _AsyncData<DataT, ErrorT> {
@@ -245,15 +248,16 @@ export function useAsyncData<
245248
}
246249

247250
// Create or use a shared asyncData entity
248-
const initialCachedData = options.getCachedData!(key.value, nuxtApp, { cause: 'initial' })
251+
const initialFetchOptions: AsyncDataExecuteOptions = { cause: 'initial', dedupe: options.dedupe }
249252
if (!nuxtApp._asyncData[key.value]?._init) {
250-
nuxtApp._asyncData[key.value] = createAsyncData(nuxtApp, key.value, _handler, options, initialCachedData)
253+
initialFetchOptions.cachedData = options.getCachedData!(key.value, nuxtApp, { cause: 'initial' })
254+
nuxtApp._asyncData[key.value] = createAsyncData(nuxtApp, key.value, _handler, options, initialFetchOptions.cachedData)
251255
}
252256
const asyncData = nuxtApp._asyncData[key.value]!
253257

254258
asyncData._deps++
255259

256-
const initialFetch = () => nuxtApp._asyncData[key.value]!.execute({ cause: 'initial', dedupe: options.dedupe })
260+
const initialFetch = () => nuxtApp._asyncData[key.value]!.execute(initialFetchOptions)
257261

258262
const fetchOnServer = options.server !== false && nuxtApp.payload.serverRendered
259263

@@ -292,7 +296,7 @@ export function useAsyncData<
292296

293297
const isWithinClientOnly = instance && (instance._nuxtClientOnly || inject(clientOnlySymbol, false))
294298

295-
if (fetchOnServer && nuxtApp.isHydrating && (asyncData.error.value || typeof initialCachedData !== 'undefined')) {
299+
if (fetchOnServer && nuxtApp.isHydrating && (asyncData.error.value || asyncData.data.value !== asyncDataDefaults.value)) {
296300
// 1. Hydration (server: true): no fetch
297301
if (pendingWhenIdle) {
298302
asyncData.pending.value = false
@@ -333,12 +337,14 @@ export function useAsyncData<
333337
if (oldKey) {
334338
unregister(oldKey)
335339
}
340+
const initialFetchOptions: AsyncDataExecuteOptions = { cause: 'initial', dedupe: options.dedupe }
336341
if (!nuxtApp._asyncData[newKey]?._init) {
337-
nuxtApp._asyncData[newKey] = createAsyncData(nuxtApp, newKey, _handler, options, options.getCachedData!(newKey, nuxtApp, { cause: 'initial' }))
342+
initialFetchOptions.cachedData = options.getCachedData!(newKey, nuxtApp, { cause: 'initial' })
343+
nuxtApp._asyncData[newKey] = createAsyncData(nuxtApp, newKey, _handler, options, initialFetchOptions.cachedData)
338344
}
339345
nuxtApp._asyncData[newKey]._deps++
340346
if (options.immediate || hasRun) {
341-
nuxtApp._asyncData[newKey].execute({ cause: 'initial', dedupe: options.dedupe })
347+
nuxtApp._asyncData[newKey].execute(initialFetchOptions)
342348
}
343349
}, { flush: 'sync' })
344350

@@ -526,7 +532,7 @@ export function clearNuxtData (keys?: string | string[] | ((key: string) => bool
526532

527533
function clearNuxtDataByKey (nuxtApp: NuxtApp, key: string): void {
528534
if (key in nuxtApp.payload.data) {
529-
nuxtApp.payload.data[key] = undefined
535+
nuxtApp.payload.data[key] = asyncDataDefaults.value
530536
}
531537

532538
if (key in nuxtApp.payload._errors) {
@@ -588,7 +594,7 @@ function createAsyncData<
588594
}
589595

590596
const _ref = options.deep ? ref : shallowRef
591-
const hasCachedData = typeof initialCachedData !== 'undefined'
597+
const hasCachedData = initialCachedData !== asyncDataDefaults.value
592598
const unsubRefreshAsyncData = nuxtApp.hook('app:data:refresh', async (keys) => {
593599
if (!keys || keys.includes(key)) {
594600
await asyncData.execute({ cause: 'refresh:hook' })
@@ -609,9 +615,9 @@ function createAsyncData<
609615
}
610616
// Avoid fetching same key that is already fetched
611617
if (granularCachedData || opts.cause === 'initial' || nuxtApp.isHydrating) {
612-
const cachedData = opts.cause === 'initial' ? initialCachedData : options.getCachedData!(key, nuxtApp, { cause: opts.cause ?? 'refresh:manual' })
613-
if (typeof cachedData !== 'undefined') {
614-
nuxtApp.payload.data[key] = asyncData.data.value = cachedData
618+
const cachedData = 'cachedData' in opts ? opts.cachedData : options.getCachedData!(key, nuxtApp, { cause: opts.cause ?? 'refresh:manual' })
619+
if (cachedData !== asyncDataDefaults.value) {
620+
nuxtApp.payload.data[key] = asyncData.data.value = cachedData as DataT
615621
asyncData.error.value = asyncDataDefaults.errorValue
616622
asyncData.status.value = 'success'
617623
return Promise.resolve(cachedData)

test/nuxt/composables.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { flushPromises } from '@vue/test-utils'
1111
import { createClientPage } from '../../packages/nuxt/src/components/runtime/client-component'
1212
import * as composables from '#app/composables'
1313

14+
import type { NuxtApp } from '#app/nuxt'
1415
import { clearNuxtData, refreshNuxtData, useAsyncData, useNuxtData } from '#app/composables/asyncData'
1516
import { clearError, createError, isNuxtError, showError, useError } from '#app/composables/error'
1617
import { onNuxtReady } from '#app/composables/ready'
@@ -647,6 +648,41 @@ describe('useAsyncData', () => {
647648
fetchData.value = 'another value'
648649
expect(nuxtData.value).toMatchInlineSnapshot('"another value"')
649650
})
651+
652+
it('duplicate calls are not made after first call has finished', async () => {
653+
const handler = vi.fn(() => Promise.resolve('hello'))
654+
const getCachedData = vi.fn((key: string, nuxtApp: NuxtApp) => {
655+
console.log(nuxtApp.payload.data[key] ? 'has data' : 'does not have data')
656+
return nuxtApp.payload.data[key]
657+
})
658+
659+
function testAsyncData () {
660+
return useAsyncData(uniqueKey, handler, {
661+
getCachedData,
662+
})
663+
}
664+
665+
const { status, data } = await testAsyncData()
666+
expect(status.value).toBe('success')
667+
expect(data.value).toBe('hello')
668+
expect(handler).toHaveBeenCalledTimes(1)
669+
expect.soft(getCachedData).toHaveBeenCalledTimes(1)
670+
671+
const { status: status2, data: data2 } = testAsyncData()
672+
expect.soft(handler).toHaveBeenCalledTimes(1)
673+
expect.soft(getCachedData).toHaveBeenCalledTimes(2)
674+
expect.soft(data.value).toBe('hello')
675+
expect.soft(data2.value).toBe('hello')
676+
expect.soft(status.value).toBe('success')
677+
expect.soft(status2.value).toBe('success')
678+
679+
await flushPromises()
680+
await nextTick()
681+
await flushPromises()
682+
683+
expect.soft(handler).toHaveBeenCalledTimes(1)
684+
expect.soft(getCachedData).toHaveBeenCalledTimes(2)
685+
})
650686
})
651687

652688
describe('useFetch', () => {

0 commit comments

Comments
 (0)
0