mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-25 15:15:19 +00:00
fix(nuxt): use shared state for asyncData (#7055)
Co-authored-by: Ohb00 <43827372+OhB00@users.noreply.github.com>
This commit is contained in:
parent
aece2cd3c2
commit
5a17458af5
@ -114,11 +114,16 @@ export function useAsyncData<
|
||||
|
||||
const useInitialCache = () => (nuxt.isHydrating || options.initialCache) && nuxt.payload.data[key] !== undefined
|
||||
|
||||
const asyncData = {
|
||||
// Create or use a shared asyncData entity
|
||||
if (!nuxt._asyncData[key]) {
|
||||
nuxt._asyncData[key] = {
|
||||
data: ref(useInitialCache() ? nuxt.payload.data[key] : options.default?.() ?? null),
|
||||
pending: ref(!useInitialCache()),
|
||||
error: ref(nuxt.payload._errors[key] ?? null)
|
||||
} as AsyncData<DataT, DataE>
|
||||
}
|
||||
}
|
||||
// TODO: Else, Soemhow check for confliciting keys with different defaults or fetcher
|
||||
const asyncData = { ...nuxt._asyncData[key] } as AsyncData<DataT, DataE>
|
||||
|
||||
asyncData.refresh = (opts = {}) => {
|
||||
// Avoid fetching same key more than once at a time
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { getCurrentInstance, reactive } from 'vue'
|
||||
import { getCurrentInstance, reactive, Ref } from 'vue'
|
||||
import type { App, onErrorCaptured, VNode } from 'vue'
|
||||
import { createHooks, Hookable } from 'hookable'
|
||||
import type { RuntimeConfig, AppConfigInput } from '@nuxt/schema'
|
||||
@ -66,6 +66,11 @@ interface _NuxtApp {
|
||||
[key: string]: any
|
||||
|
||||
_asyncDataPromises: Record<string, Promise<any> | undefined>
|
||||
_asyncData: Record<string, {
|
||||
data: Ref<any>
|
||||
pending: Ref<boolean>
|
||||
error: Ref<any>
|
||||
}>,
|
||||
|
||||
ssrContext?: NuxtSSRContext
|
||||
payload: {
|
||||
@ -113,6 +118,7 @@ export function createNuxtApp (options: CreateOptions) {
|
||||
}),
|
||||
isHydrating: process.client,
|
||||
_asyncDataPromises: {},
|
||||
_asyncData: {},
|
||||
...options
|
||||
} as any as NuxtApp
|
||||
|
||||
|
@ -527,3 +527,21 @@ describe('app config', () => {
|
||||
expect(html).toContain(JSON.stringify(expectedAppConfig))
|
||||
})
|
||||
})
|
||||
|
||||
describe('useAsyncData', () => {
|
||||
it('single request resolves', async () => {
|
||||
await expectNoClientErrors('/useAsyncData/single')
|
||||
})
|
||||
|
||||
it('two requests resolve', async () => {
|
||||
await expectNoClientErrors('/useAsyncData/double')
|
||||
})
|
||||
|
||||
it('two requests resolve and sync', async () => {
|
||||
await $fetch('/useAsyncData/refresh')
|
||||
})
|
||||
|
||||
it('two requests made at once resolve and sync', async () => {
|
||||
await expectNoClientErrors('/useAsyncData/promise-all')
|
||||
})
|
||||
})
|
||||
|
7
test/fixtures/basic/composables/asyncDataTests.ts
vendored
Normal file
7
test/fixtures/basic/composables/asyncDataTests.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export const useSleep = () => useAsyncData('sleep', async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
|
||||
return 'Slept!'
|
||||
})
|
||||
|
||||
export const useCounter = () => useFetch('/api/useAsyncData/count')
|
26
test/fixtures/basic/pages/useAsyncData/double.vue
vendored
Normal file
26
test/fixtures/basic/pages/useAsyncData/double.vue
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
Single
|
||||
<div>
|
||||
data1: {{ data1 }}
|
||||
data2: {{ data2 }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data: data1 } = await useSleep()
|
||||
const { data: data2 } = await useSleep()
|
||||
|
||||
if (data1.value === null || data1.value === undefined || data1.value.length <= 0) {
|
||||
throw new Error('Data should never be null or empty.')
|
||||
}
|
||||
|
||||
if (data2.value === null || data2.value === undefined || data2.value.length <= 0) {
|
||||
throw new Error('Data should never be null or empty.')
|
||||
}
|
||||
|
||||
if (data1.value !== data2.value) {
|
||||
throw new Error('AsyncData not synchronised')
|
||||
}
|
||||
</script>
|
31
test/fixtures/basic/pages/useAsyncData/promise-all.vue
vendored
Normal file
31
test/fixtures/basic/pages/useAsyncData/promise-all.vue
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div>
|
||||
Single
|
||||
<div>
|
||||
data1: {{ result1.data.value }}
|
||||
data2: {{ result2.data.value }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const [result1, result2] = await Promise.all([useSleep(), useSleep()])
|
||||
|
||||
if (result1.data.value === null || result1.data.value === undefined || result1.data.value.length <= 0) {
|
||||
throw new Error('Data should never be null or empty.')
|
||||
}
|
||||
|
||||
if (result2.data.value === null || result2.data.value === undefined || result2.data.value.length <= 0) {
|
||||
throw new Error('Data should never be null or empty.')
|
||||
}
|
||||
|
||||
if (result1.data.value !== result2.data.value) {
|
||||
throw new Error('AsyncData not synchronised')
|
||||
}
|
||||
|
||||
await result1.refresh()
|
||||
|
||||
if (result1.data.value !== result2.data.value) {
|
||||
throw new Error('AsyncData not synchronised')
|
||||
}
|
||||
</script>
|
39
test/fixtures/basic/pages/useAsyncData/refresh.vue
vendored
Normal file
39
test/fixtures/basic/pages/useAsyncData/refresh.vue
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
Single
|
||||
<div>
|
||||
{{ data }} - {{ data2 }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data, refresh } = await useCounter()
|
||||
const { data: data2, refresh: refresh2 } = await useCounter()
|
||||
|
||||
let inital = data.value.count
|
||||
|
||||
// Refresh on client and server side
|
||||
await refresh()
|
||||
|
||||
if (data.value.count !== inital + 1) {
|
||||
throw new Error('Data not refreshed?' + data.value.count + ' : ' + data2.value.count)
|
||||
}
|
||||
|
||||
if (data.value.count !== data2.value.count) {
|
||||
throw new Error('AsyncData not synchronised')
|
||||
}
|
||||
|
||||
inital = data.value.count
|
||||
|
||||
await refresh2()
|
||||
|
||||
if (data.value.count !== inital + 1) {
|
||||
throw new Error('data2 refresh not syncronised?')
|
||||
}
|
||||
|
||||
if (data.value.count !== data2.value.count) {
|
||||
throw new Error('AsyncData not synchronised')
|
||||
}
|
||||
|
||||
</script>
|
16
test/fixtures/basic/pages/useAsyncData/single.vue
vendored
Normal file
16
test/fixtures/basic/pages/useAsyncData/single.vue
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
Single
|
||||
<div>
|
||||
{{ data }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { data } = await useSleep()
|
||||
|
||||
if (data.value === null || data.value === undefined || data.value.length <= 0) {
|
||||
throw new Error('Data should never be null or empty.')
|
||||
}
|
||||
</script>
|
3
test/fixtures/basic/server/api/useAsyncData/count.ts
vendored
Normal file
3
test/fixtures/basic/server/api/useAsyncData/count.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
let counter = 0
|
||||
|
||||
export default () => ({ count: counter++ })
|
Loading…
Reference in New Issue
Block a user