Skip to content

options.unmount ancestor walk makes whole-tree unmount O(hook components × depth) #5217

Description

@upupming

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions