Skip to content

Commit 1b8e197

Browse files
committed
fix(suspense/hydration): fix hydration timing of async component inside suspense
close #6638
1 parent e255c31 commit 1b8e197

File tree

2 files changed

+54
-3
lines changed

2 files changed

+54
-3
lines changed

Diff for: packages/runtime-core/__tests__/hydration.spec.ts

+48
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,54 @@ describe('SSR hydration', () => {
688688
expect(container.innerHTML).toBe(`<span>1</span>`)
689689
})
690690

691+
// #6638
692+
test('Suspense + async component', async () => {
693+
let isSuspenseResolved = false
694+
let isSuspenseResolvedInChild: any
695+
const AsyncChild = defineAsyncComponent(() =>
696+
Promise.resolve(
697+
defineComponent({
698+
setup() {
699+
isSuspenseResolvedInChild = isSuspenseResolved
700+
const count = ref(0)
701+
return () =>
702+
h(
703+
'span',
704+
{
705+
onClick: () => {
706+
count.value++
707+
},
708+
},
709+
count.value,
710+
)
711+
},
712+
}),
713+
),
714+
)
715+
const { vnode, container } = mountWithHydration('<span>0</span>', () =>
716+
h(
717+
Suspense,
718+
{
719+
onResolve() {
720+
isSuspenseResolved = true
721+
},
722+
},
723+
() => h(AsyncChild),
724+
),
725+
)
726+
expect(vnode.el).toBe(container.firstChild)
727+
// wait for hydration to finish
728+
await new Promise(r => setTimeout(r))
729+
730+
expect(isSuspenseResolvedInChild).toBe(false)
731+
expect(isSuspenseResolved).toBe(true)
732+
733+
// assert interaction
734+
triggerEvent('click', container.querySelector('span')!)
735+
await nextTick()
736+
expect(container.innerHTML).toBe(`<span>1</span>`)
737+
})
738+
691739
test('Suspense (full integration)', async () => {
692740
const mountedCalls: number[] = []
693741
const asyncDeps: Promise<any>[] = []

Diff for: packages/runtime-core/src/renderer.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,7 @@ function baseCreateRenderer(
12761276
const componentUpdateFn = () => {
12771277
if (!instance.isMounted) {
12781278
let vnodeHook: VNodeHook | null | undefined
1279-
const { el, props } = initialVNode
1279+
const { el, props, type } = initialVNode
12801280
const { bm, m, parent } = instance
12811281
const isAsyncWrapperVNode = isAsyncWrapper(initialVNode)
12821282

@@ -1325,8 +1325,11 @@ function baseCreateRenderer(
13251325
}
13261326
}
13271327

1328-
if (isAsyncWrapperVNode) {
1329-
;(initialVNode.type as ComponentOptions).__asyncLoader!().then(
1328+
if (
1329+
isAsyncWrapperVNode &&
1330+
!(type as ComponentOptions).__asyncResolved
1331+
) {
1332+
;(type as ComponentOptions).__asyncLoader!().then(
13301333
// note: we are moving the render call into an async callback,
13311334
// which means it won't track dependencies - but it's ok because
13321335
// a server-rendered async wrapper is already in resolved state

0 commit comments

Comments
 (0)