From d91dd89c37938cc6febe3c655090aefe942be845 Mon Sep 17 00:00:00 2001 From: tsushanth <78000697+tsushanth@users.noreply.github.com> Date: Wed, 24 Jun 2026 17:30:07 -0700 Subject: [PATCH] fix(portal): guard removeChild against externally cleared mount If the portal mount node is cleared externally (e.g. via innerHTML = "") before Solid's cleanup runs, the onCleanup callback would throw a NotFoundError because the container is no longer a child of el. Guard the removeChild call with el.contains(container) so disposal is a no-op when the container has already been removed from the DOM. Fixes #2775 --- packages/solid/web/src/index.ts | 2 +- packages/solid/web/test/portal.spec.tsx | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/solid/web/src/index.ts b/packages/solid/web/src/index.ts index ba07a58cb..0d2d44e82 100644 --- a/packages/solid/web/src/index.ts +++ b/packages/solid/web/src/index.ts @@ -101,7 +101,7 @@ export function Portal(pro insert(renderRoot, content); el.appendChild(container); props.ref && (props as any).ref(container); - onCleanup(() => el.removeChild(container)); + onCleanup(() => el.contains(container) && el.removeChild(container)); } }, undefined, diff --git a/packages/solid/web/test/portal.spec.tsx b/packages/solid/web/test/portal.spec.tsx index 9cae05b1c..c234c6a11 100644 --- a/packages/solid/web/test/portal.spec.tsx +++ b/packages/solid/web/test/portal.spec.tsx @@ -115,6 +115,24 @@ describe("Testing a Portal with Synthetic Events", () => { test("dispose", () => disposer()); }); +describe("Testing a Portal whose mount is externally cleared before disposal", () => { + let div = document.createElement("div"), + disposer: () => void; + const testMount = document.createElement("div"); + const Component = () => Hi; + + test("Create portal", () => { + disposer = render(Component, div); + expect((testMount.firstChild as HTMLDivElement).innerHTML).toBe("Hi"); + }); + + test("dispose does not throw when mount has been externally emptied", () => { + // Simulate an external actor clearing the mount node (e.g. innerHTML = "") + testMount.innerHTML = ""; + expect(() => disposer()).not.toThrow(); + }); +}); + describe("Testing a Portal with direct reactive children", () => { let div = document.createElement("div"), disposer: () => void,