diff --git a/packages/runtime-vapor/__tests__/hydration.spec.ts b/packages/runtime-vapor/__tests__/hydration.spec.ts
index 0c0919ae4..54381d7e6 100644
--- a/packages/runtime-vapor/__tests__/hydration.spec.ts
+++ b/packages/runtime-vapor/__tests__/hydration.spec.ts
@@ -1134,6 +1134,28 @@ describe('Vapor Mode hydration', () => {
expect(container.innerHTML).toBe(`
foo
`)
})
+ test('v-if on insertion parent', async () => {
+ const data = ref(true)
+ const { container } = await testHydration(
+ `
+
+
+
+ `,
+ { Child: `foo` },
+ data,
+ )
+ expect(container.innerHTML).toBe(`foo
`)
+
+ data.value = false
+ await nextTick()
+ expect(container.innerHTML).toBe(``)
+
+ data.value = true
+ await nextTick()
+ expect(container.innerHTML).toBe(`foo
`)
+ })
+
test('v-if/else-if/else chain - switch branches', async () => {
const data = ref('a')
const { container } = await testHydration(
diff --git a/packages/runtime-vapor/src/apiCreateIf.ts b/packages/runtime-vapor/src/apiCreateIf.ts
index 56b9d9b4a..bfa9e6e7a 100644
--- a/packages/runtime-vapor/src/apiCreateIf.ts
+++ b/packages/runtime-vapor/src/apiCreateIf.ts
@@ -9,6 +9,37 @@ import {
import { renderEffect } from './renderEffect'
import { DynamicFragment } from './fragment'
+const ifStack = [] as DynamicFragment[]
+const insertionParents = new WeakMap()
+
+/**
+ * Collects insertionParents inside an if block during hydration
+ * When the if condition becomes false on the client, clears the
+ * HTML of these insertionParents to prevent duplicate rendering
+ * results when the condition becomes true again
+ *
+ * Example:
+ * const t2 = _template("")
+ * const n2 = _createIf(() => show.value, () => {
+ * const n5 = t2()
+ * _setInsertionState(n5)
+ * const n4 = _createComponent(Comp) // renders ``
+ * return n5
+ * })
+ *
+ * After hydration, the HTML of `n5` is `
` instead of ``.
+ * When `show.value` becomes false, the HTML of `n5` needs to be cleared,
+ * to avoid duplicated rendering when `show.value` becomes true again.
+ */
+export function collectInsertionParents(insertionParent: ParentNode): void {
+ const currentIf = ifStack[ifStack.length - 1]
+ if (currentIf) {
+ let nodes = insertionParents.get(currentIf)
+ if (!nodes) insertionParents.set(currentIf, (nodes = []))
+ nodes.push(insertionParent)
+ }
+}
+
export function createIf(
condition: () => any,
b1: BlockFn,
@@ -27,7 +58,19 @@ export function createIf(
isHydrating || __DEV__
? new DynamicFragment(IF_ANCHOR_LABEL)
: new DynamicFragment()
+ if (isHydrating) {
+ ;(frag as DynamicFragment).teardown = () => {
+ const nodes = insertionParents.get(frag as DynamicFragment)
+ if (nodes) {
+ nodes.forEach(p => ((p as Element).innerHTML = ''))
+ insertionParents.delete(frag as DynamicFragment)
+ }
+ ;(frag as DynamicFragment).teardown = undefined
+ }
+ ifStack.push(frag as DynamicFragment)
+ }
renderEffect(() => (frag as DynamicFragment).update(condition() ? b1 : b2))
+ isHydrating && ifStack.pop()
}
if (!isHydrating && _insertionParent) {
diff --git a/packages/runtime-vapor/src/fragment.ts b/packages/runtime-vapor/src/fragment.ts
index 9f51fac38..50ce18bdb 100644
--- a/packages/runtime-vapor/src/fragment.ts
+++ b/packages/runtime-vapor/src/fragment.ts
@@ -59,6 +59,7 @@ export class DynamicFragment extends VaporFragment {
* indicates forwarded slot
*/
forwarded?: boolean
+ teardown?: () => void
constructor(anchorLabel?: string) {
super([])
@@ -97,6 +98,7 @@ export class DynamicFragment extends VaporFragment {
// teardown previous branch
if (this.scope) {
this.scope.stop()
+ if (parent) this.teardown && this.teardown()
const mode = transition && transition.mode
if (mode) {
applyTransitionLeaveHooks(this.nodes, transition, renderBranch)
diff --git a/packages/runtime-vapor/src/insertionState.ts b/packages/runtime-vapor/src/insertionState.ts
index 5c4c41fe2..5c8ba4262 100644
--- a/packages/runtime-vapor/src/insertionState.ts
+++ b/packages/runtime-vapor/src/insertionState.ts
@@ -1,3 +1,6 @@
+import { collectInsertionParents } from './apiCreateIf'
+import { isHydrating } from './dom/hydration'
+
export let insertionParent:
| (ParentNode & {
// dynamic node position - hydration only
@@ -21,6 +24,10 @@ export let insertionAnchor: Node | 0 | undefined
export function setInsertionState(parent: ParentNode, anchor?: Node | 0): void {
insertionParent = parent
insertionAnchor = anchor
+
+ if (isHydrating) {
+ collectInsertionParents(parent)
+ }
}
export function resetInsertionState(): void {