From 80b64c440c3459ddddd48e56e0984f4a7d4403d9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 10 Mar 2026 14:22:34 +0000 Subject: [PATCH] :bug: Fix removeChild crash on portal-on-document* unmount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous implementation passed document.body directly as the React portal containerInfo. During unmount, React's commit phase (commitUnmountFiberChildrenRecursively, case 4) sets the current container to containerInfo and then calls container.removeChild() for every DOM node inside the portal tree. When two concurrent state updates are processed — e.g. navigating away from a dashboard section while a file-menu portal is open — React could attempt document.body.removeChild(node) twice for the same node, the second time throwing: NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node. The fix allocates a dedicated
container per portal instance via mf/use-memo. The container is appended to body on mount and removed in the effect cleanup. React then owns an exclusive containerInfo and its unmount path never races with another portal or the modal container (which also targets document.body). Signed-off-by: Andrey Antukh --- frontend/src/app/main/ui/components/portal.cljs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/components/portal.cljs b/frontend/src/app/main/ui/components/portal.cljs index 27ebaa205a..ff9f3558d4 100644 --- a/frontend/src/app/main/ui/components/portal.cljs +++ b/frontend/src/app/main/ui/components/portal.cljs @@ -11,6 +11,11 @@ (mf/defc portal-on-document* [{:keys [children]}] - (mf/portal - (mf/html [:* children]) - (dom/get-body))) + (let [container (mf/use-memo #(dom/create-element "div"))] + (mf/with-effect [] + (let [body (dom/get-body)] + (dom/append-child! body container) + #(dom/remove-child! body container))) + (mf/portal + (mf/html [:* children]) + container)))