diff --git a/frontend/src/app/main/ui/components/portal.cljs b/frontend/src/app/main/ui/components/portal.cljs index ff9f3558d4..381db4b66c 100644 --- a/frontend/src/app/main/ui/components/portal.cljs +++ b/frontend/src/app/main/ui/components/portal.cljs @@ -6,16 +6,12 @@ (ns app.main.ui.components.portal (:require - [app.util.dom :as dom] + [app.main.ui.hooks :as hooks] [rumext.v2 :as mf])) (mf/defc portal-on-document* [{:keys [children]}] - (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))) + (let [container (hooks/use-portal-container)] (mf/portal (mf/html [:* children]) container))) diff --git a/frontend/src/app/main/ui/ds/tooltip/tooltip.cljs b/frontend/src/app/main/ui/ds/tooltip/tooltip.cljs index f1598364d4..74764a1a4c 100644 --- a/frontend/src/app/main/ui/ds/tooltip/tooltip.cljs +++ b/frontend/src/app/main/ui/ds/tooltip/tooltip.cljs @@ -9,6 +9,7 @@ [app.main.style :as stl]) (:require [app.common.data :as d] + [app.main.ui.hooks :as hooks] [app.util.dom :as dom] [app.util.keyboard :as kbd] [app.util.timers :as ts] @@ -159,7 +160,7 @@ tooltip-ref (mf/use-ref nil) - container (mf/use-memo #(dom/create-element "div")) + container (hooks/use-portal-container) id (d/nilv id internal-id) @@ -246,11 +247,6 @@ content aria-label)})] - (mf/with-effect [] - (let [body (dom/get-body)] - (dom/append-child! body container) - #(dom/remove-child! body container))) - (mf/use-effect (mf/deps tooltip-id) (fn [] diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index b4ad8fe616..42560cd8fe 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -380,6 +380,18 @@ state)) +(defn use-portal-container + "Creates a dedicated div container for React portals. The container + is appended to document.body on mount and removed on cleanup, preventing + removeChild race conditions when multiple portals target the same 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))) + container)) + (defn use-dynamic-grid-item-width ([] (use-dynamic-grid-item-width nil)) ([itemsize] diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index ce09cc71da..5df1cc3daa 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.main.data.modal :as modal] [app.main.store :as st] + [app.main.ui.hooks :as hooks] [app.util.dom :as dom] [app.util.keyboard :as k] [goog.events :as events] @@ -83,11 +84,7 @@ (mf/defc modal-container* {::mf/props :obj} [] - (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))) + (let [container (hooks/use-portal-container)] (when-let [modal (mf/deref ref:modal)] (mf/portal (mf/html [:> modal-wrapper* {:data modal :key (dm/str (:id modal))}]) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs index 22e1915e64..0dd980f73f 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/context_menu.cljs @@ -20,6 +20,7 @@ [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] + [app.main.ui.hooks :as hooks] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [app.util.timers :as timers] @@ -516,12 +517,7 @@ dropdown-direction-change* (mf/use-ref 0) top (+ (get-in mdata [:position :y]) 5) left (+ (get-in mdata [:position :x]) 5) - 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))) + container (hooks/use-portal-container)] (mf/use-effect (mf/deps is-open?) diff --git a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs index 2c5b4e25cd..3e6dec43b1 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/forms/controls/combobox.cljs @@ -19,6 +19,7 @@ [app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown*]] [app.main.ui.ds.foundations.assets.icon :as i] [app.main.ui.forms :as fc] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.tokens.management.forms.controls.combobox-navigation :refer [use-navigation]] [app.main.ui.workspace.tokens.management.forms.controls.floating-dropdown :refer [use-floating-dropdown]] [app.main.ui.workspace.tokens.management.forms.controls.token-parsing :as tp] @@ -92,7 +93,7 @@ icon-button-ref (mf/use-ref nil) ref (or ref internal-ref) - container (mf/use-memo #(dom/create-element "div")) + container (hooks/use-portal-container) raw-tokens-by-type (mf/use-ctx muc/active-tokens-by-type) @@ -269,11 +270,6 @@ (mf/with-effect [dropdown-options] (mf/set-ref-val! options-ref dropdown-options)) - (mf/with-effect [] - (let [body (dom/get-body)] - (dom/append-child! body container) - #(dom/remove-child! body container))) - (mf/with-effect [is-open* ref wrapper-ref] (when is-open (let [handler (fn [event] diff --git a/frontend/src/app/main/ui/workspace/tokens/management/node_context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/management/node_context_menu.cljs index c9b6947316..d37e628d02 100644 --- a/frontend/src/app/main/ui/workspace/tokens/management/node_context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/management/node_context_menu.cljs @@ -6,6 +6,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.hooks :as hooks] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [okulary.core :as l] @@ -35,7 +36,7 @@ dropdown-direction-change* (mf/use-ref 0) top (+ (get-in mdata [:position :y]) 5) left (+ (get-in mdata [:position :x]) 5) - container (mf/use-memo #(dom/create-element "div")) + container (hooks/use-portal-container) delete-node (mf/use-fn (mf/deps mdata) @@ -45,11 +46,6 @@ (when node (on-delete-node node type)))))] - (mf/with-effect [] - (let [body (dom/get-body)] - (dom/append-child! body container) - #(dom/remove-child! body container))) - (mf/with-effect [is-open?] (when (and (not= 0 (mf/ref-val dropdown-direction-change*)) (= false is-open?)) (reset! dropdown-direction* "down") diff --git a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs index 35b775c9c6..a8687c9719 100644 --- a/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/themes/theme_selector.cljs @@ -17,6 +17,7 @@ [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] [app.main.ui.ds.foundations.typography.text :refer [text*]] + [app.main.ui.hooks :as hooks] [app.util.dom :as dom] [app.util.i18n :refer [tr]] [cuerdas.core :as str] @@ -113,12 +114,7 @@ :is-open? true :rect rect)))))) - 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))) + container (hooks/use-portal-container)] [:div {:on-click on-open-dropdown :disabled (not can-edit?)