diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts
index 4a9e0fac2b6..43af0583ce4 100644
--- a/packages/runtime-core/__tests__/hydration.spec.ts
+++ b/packages/runtime-core/__tests__/hydration.spec.ts
@@ -1160,6 +1160,69 @@ describe('SSR hydration', () => {
)
})
+ // #13510
+ test('update async component after parent mount before async component resolve', async () => {
+ const Comp = {
+ props: ['toggle'],
+ render(this: any) {
+ return h('h1', [
+ this.toggle ? 'Async component' : 'Updated async component',
+ ])
+ },
+ }
+ let serverResolve: any
+ let AsyncComp = defineAsyncComponent(
+ () =>
+ new Promise(r => {
+ serverResolve = r
+ }),
+ )
+
+ const toggle = ref(true)
+ const App = {
+ setup() {
+ onMounted(() => {
+ // change state, after mount and before async component resolve
+ nextTick(() => (toggle.value = false))
+ })
+
+ return () => {
+ return h(AsyncComp, { toggle: toggle.value })
+ }
+ },
+ }
+
+ // server render
+ const htmlPromise = renderToString(h(App))
+ serverResolve(Comp)
+ const html = await htmlPromise
+ expect(html).toMatchInlineSnapshot(`"
Async component
"`)
+
+ // hydration
+ let clientResolve: any
+ AsyncComp = defineAsyncComponent(
+ () =>
+ new Promise(r => {
+ clientResolve = r
+ }),
+ )
+
+ const container = document.createElement('div')
+ container.innerHTML = html
+ createSSRApp(App).mount(container)
+
+ // resolve
+ clientResolve(Comp)
+ await new Promise(r => setTimeout(r))
+
+ // prevent lazy hydration since the component has been patched
+ expect('Skipping lazy hydration for component').toHaveBeenWarned()
+ expect(`Hydration node mismatch`).not.toHaveBeenWarned()
+ expect(container.innerHTML).toMatchInlineSnapshot(
+ `"Updated async component
"`,
+ )
+ })
+
test('hydrate safely when property used by async setup changed before render', async () => {
const toggle = ref(true)
diff --git a/packages/runtime-core/src/apiAsyncComponent.ts b/packages/runtime-core/src/apiAsyncComponent.ts
index cb675f06e43..25e6fa481ca 100644
--- a/packages/runtime-core/src/apiAsyncComponent.ts
+++ b/packages/runtime-core/src/apiAsyncComponent.ts
@@ -123,28 +123,28 @@ export function defineAsyncComponent<
__asyncHydrate(el, instance, hydrate) {
let patched = false
+ ;(instance.bu || (instance.bu = [])).push(() => (patched = true))
+ const performHydrate = () => {
+ // skip hydration if the component has been patched
+ if (__DEV__ && patched) {
+ warn(
+ `Skipping lazy hydration for component '${getComponentName(resolvedComp!) || resolvedComp!.__file}': ` +
+ `it was updated before lazy hydration performed.`,
+ )
+ return
+ }
+ hydrate()
+ }
const doHydrate = hydrateStrategy
? () => {
- const performHydrate = () => {
- // skip hydration if the component has been patched
- if (__DEV__ && patched) {
- warn(
- `Skipping lazy hydration for component '${getComponentName(resolvedComp!)}': ` +
- `it was updated before lazy hydration performed.`,
- )
- return
- }
- hydrate()
- }
const teardown = hydrateStrategy(performHydrate, cb =>
forEachElement(el, cb),
)
if (teardown) {
;(instance.bum || (instance.bum = [])).push(teardown)
}
- ;(instance.u || (instance.u = [])).push(() => (patched = true))
}
- : hydrate
+ : performHydrate
if (resolvedComp) {
doHydrate()
} else {