diff --git a/src/adapter/10/bindings.ts b/src/adapter/10/bindings.ts index fee5c930..ed30fa6e 100644 --- a/src/adapter/10/bindings.ts +++ b/src/adapter/10/bindings.ts @@ -8,6 +8,17 @@ import { ComponentHooks, HookState, PreactBindings } from "../shared/bindings"; import { RendererConfig } from "../shared/renderer"; import { getRenderReasonPost } from "./renderReason"; +function getVirtualParent(vnode: VNode): VNode | null { + const vnodeChildren = getActualChildren(vnode); + if (vnodeChildren.length > 0 && isVNode(vnodeChildren[0])) { + const child = vnodeChildren[0]; + if ((child as any).__linked_parent) { + return (child as any).__linked_parent; + } + } + return null; +} + // Mangle accessors /** @@ -15,6 +26,7 @@ import { getRenderReasonPost } from "./renderReason"; */ export function getVNodeParent(vnode: VNode): VNode | null { return ( + getVirtualParent(vnode) || (vnode as IVNode)._parent || (vnode as any).__ || // Older Preact X versions used `__p` @@ -172,7 +184,9 @@ export function getHookState( export function getActualChildren( vnode: VNode, ): Array { - return (vnode as IVNode)._children || (vnode as any).__k || []; + const children = (vnode as IVNode)._children || (vnode as any).__k || []; + + return [...children, ...((vnode as any).__linked_children || [])]; } // End Mangle accessors diff --git a/src/adapter/11/bindings.ts b/src/adapter/11/bindings.ts index 58fdbdca..d216d590 100644 --- a/src/adapter/11/bindings.ts +++ b/src/adapter/11/bindings.ts @@ -169,7 +169,10 @@ export function getDisplayName(internal: Internal, config: RendererConfig) { export function getActualChildren( internal: Internal, ): Array { - return (internal as Internal)._children || (internal as any).__k || []; + const children = + (internal as Internal)._children || (internal as any).__k || []; + + return [...children, ...((internal as any).__linked_children || [])]; } export function getComponent(node: HookState | Internal): Component | null { @@ -192,11 +195,27 @@ export function getDom(internal: Internal): HTMLElement | Text | null { return (internal as Internal)._dom || (internal as any).__e || null; } +function getVirtualParent(vnode: Internal): Internal | null { + const vnodeChildren = getActualChildren(vnode); + if (vnodeChildren.length > 0 && isInternal(vnodeChildren[0])) { + const child = vnodeChildren[0]; + if ((child as any).__linked_parent) { + return (child as any).__linked_parent; + } + } + return null; +} + /** * Get the direct parent of a `vnode` */ export function getVNodeParent(internal: Internal): Internal | null { - return (internal as Internal)._parent || (internal as any).__ || null; + return ( + getVirtualParent(internal) || + (internal as Internal)._parent || + (internal as any).__ || + null + ); } /** diff --git a/src/adapter/shared/traverse.ts b/src/adapter/shared/traverse.ts index d445451d..b8ed3d5b 100644 --- a/src/adapter/shared/traverse.ts +++ b/src/adapter/shared/traverse.ts @@ -183,6 +183,15 @@ export function shouldFilter( ) { return true; } + // This is something like: when using linked parents / children, they come from + // separate commits, but they are linked, so will have children / parents set. + // Removing this results in duplicates on first start (interestingly, not + // duplicates when you toggle the "Fragments" element filter. I think this is + // because it then recreates the tree from children, and doesn't have the + // problem of creating this from separate commits) + if ((vnode as any).__linked_parent && !bindings.getComponent(vnode)) { + return true; + } return false; } diff --git a/test-e2e/fixtures/apps/root-multi-nested.jsx b/test-e2e/fixtures/apps/root-multi-nested.jsx new file mode 100644 index 00000000..d09628b3 --- /dev/null +++ b/test-e2e/fixtures/apps/root-multi-nested.jsx @@ -0,0 +1,51 @@ +import { h, render } from "preact"; +import { useEffect, useState } from "preact/hooks"; + +function Display(props) { + return
Counter: {props.value}
; +} + +function Child(props) { + return

Child {props.id}

; +} + +function linkRoots(parent, child) { + // v10 linking + parent.__linked_children = [child]; + child.__linked_parent = parent; +} + +function Counter({ id }) { + const [v, set] = useState(0); + + const ChildRoot1 = () =>
; + const ChildRoot2 = () =>
; + + const childRoot1Vnode = ; + const childRoot2Vnode = ; + + useEffect(() => { + const child1 = ; + const child2 = ; + + linkRoots(childRoot1Vnode, child1); + linkRoots(childRoot2Vnode, child2); + + render(child1, document.getElementById("child-1")); + render(child2, document.getElementById("child-2")); + }, []); + + return ( +
+

{id}

+ + + + {childRoot1Vnode} + {childRoot2Vnode} +
+ ); +} +const app1 = ; + +render(app1, document.getElementById("app"));