We're the ReactLynx team (lynx-family/lynx-stack) — ReactLynx ships a Preact fork as its renderer. Migrating from 10.x to 11 regressed page teardown.
46ddd2f9 added this walk to options.unmount in hooks, to find the nearest still-mounted ancestor:
let errorParent = vnode._parent;
while (errorParent && !(errorParent._component && errorParent._component._parentDom)) {
errorParent = errorParent._parent;
}
During a full unmount there is no such ancestor. unmount() clears _parentDom on the way down:
render(null, container)
unmount(App) App._parentDom = null
unmount(Wrapper) Wrapper._parentDom = null
unmount(Leaf) ← walk runs here, every ancestor already null
So the walk always reaches the root and yields null. Subtree unmounts are fine — the parent above the removed root is still mounted, so it stops on step one.
Benchmarks
Our destroyBackground suite splits on one thing: does the fixture use hooks? (full CodSpeed report on our PR, Simulation mode = instruction counts, not timing noise.)
| benchmark |
hooks |
10.x → 11.0.0-rc.1 |
008-many-use-state |
1000 components, one useState each |
-6.04% |
015-attrs-component |
none |
+5.44% |
002-hello-reactLynx |
none |
+5.37% |
012-attrs-compile |
none |
+5.34% |
rc.1 made everything ~5% faster. Only the hook fixture regressed — matching where the walk sits, inside if (c && c.__hooks). Instrumenting it during a real page destroy: { calls: 1000, iters: 3000 }, i.e. 1000 components × 3 levels, exactly the tree depth.
Repro
lynx-family/internal-preact@c61f5b1 — a test/browser test with a fixed leaf count, varying only depth:
| depth |
11.0.0-rc.1 |
10.29.8 |
| 1 |
1.70ms |
1.80ms |
| 60 |
3.80ms (2.24x) |
1.80ms (1.00x) |
Every component in a full unmount resolves to the same errorParent, so it could presumably be computed once per pass. We don't know why the parentVNode argument was dropped, so we're reporting rather than proposing a patch.
We're the ReactLynx team (lynx-family/lynx-stack) — ReactLynx ships a Preact fork as its renderer. Migrating from 10.x to 11 regressed page teardown.
46ddd2f9added this walk tooptions.unmountinhooks, to find the nearest still-mounted ancestor:During a full unmount there is no such ancestor.
unmount()clears_parentDomon the way down:So the walk always reaches the root and yields
null. Subtree unmounts are fine — the parent above the removed root is still mounted, so it stops on step one.Benchmarks
Our
destroyBackgroundsuite splits on one thing: does the fixture use hooks? (full CodSpeed report on our PR, Simulation mode = instruction counts, not timing noise.)008-many-use-stateuseStateeach015-attrs-component002-hello-reactLynx012-attrs-compilerc.1 made everything ~5% faster. Only the hook fixture regressed — matching where the walk sits, inside
if (c && c.__hooks). Instrumenting it during a real page destroy:{ calls: 1000, iters: 3000 }, i.e. 1000 components × 3 levels, exactly the tree depth.Repro
lynx-family/internal-preact@c61f5b1 — a
test/browsertest with a fixed leaf count, varying only depth:Every component in a full unmount resolves to the same
errorParent, so it could presumably be computed once per pass. We don't know why theparentVNodeargument was dropped, so we're reporting rather than proposing a patch.