diff --git a/.serena/memories/frontend/ui-conventions-and-style-system.md b/.serena/memories/frontend/ui-conventions-and-style-system.md index 4212d88029..1161b55d3a 100644 --- a/.serena/memories/frontend/ui-conventions-and-style-system.md +++ b/.serena/memories/frontend/ui-conventions-and-style-system.md @@ -33,6 +33,7 @@ ## Performance - Keep expensive derived data in refs, memoized selectors, or pure helpers. In hot render paths, prefer existing `app.common.data.macros` helpers where local code already uses them. +- Derive index-aware or sorted/filtered sequences once: wrap the transformation in `mf/with-memo` keyed on the source collection instead of calling `d/enumerate` in the render body. Use the shared `d/xf:add-index`, which attaches `:app.common.data/index` to each item; the items must be associative (maps/records), so it does not work on keywords or plain ids. - Avoid creating new callback functions/objects inside hot renders when a named function, memoized callback, data attribute, or precomputed JS props object works. - Destructure props/state values used repeatedly. Avoid repeated deref/property access in render loops. diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 64da2a7d6a..8a1efbe5d3 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -41,6 +41,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def xf:map-id (map :id)) +(def xf:add-index (map-indexed (fn [index item] (assoc item ::index index)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Structures diff --git a/common/test/common_tests/data_test.cljc b/common/test/common_tests/data_test.cljc index cd3e1c5eae..353435bab8 100644 --- a/common/test/common_tests/data_test.cljc +++ b/common/test/common_tests/data_test.cljc @@ -1031,3 +1031,31 @@ ;; Clamp out-of-range positions (t/is (= [:b :c :d :a] (d/reorder [:a :b :c :d] 0 100))) (t/is (= [:a :b :c :d] (d/reorder [:a :b :c :d] -5 0)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Commonly used transducers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(t/deftest xf-add-index-adds-index-to-each-item + (t/is (= [{:id 1 ::d/index 0} + {:id 2 ::d/index 1} + {:id 3 ::d/index 2}] + (into [] d/xf:add-index [{:id 1} {:id 2} {:id 3}])))) + +(t/deftest xf-add-index-preserves-input-order + (t/is (= [0 1 2] + (mapv ::d/index (into [] d/xf:add-index [{:id :c} {:id :a} {:id :b}]))))) + +(t/deftest xf-add-index-handles-empty-and-nil-input + (t/is (= [] (into [] d/xf:add-index []))) + (t/is (= [] (into [] d/xf:add-index nil)))) + +(t/deftest xf-add-index-composes-with-other-transducers + ;; The index is assigned after previous stages run, so it always + ;; reflects the position in the output of the chain. + (t/is (= [{:id 1 :keep? true ::d/index 0} + {:id 3 :keep? true ::d/index 1}] + (into [] (comp (filter :keep?) d/xf:add-index) + [{:id 1 :keep? true} + {:id 2 :keep? false} + {:id 3 :keep? true}])))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs index 4f2eb3918f..0872dae226 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs @@ -710,6 +710,10 @@ (let [show-content* (mf/use-state true) show-content? (deref show-content*) + interactions + (mf/with-memo [interactions] + (into [] d/xf:add-index interactions)) + toggle-content (mf/use-fn #(swap! show-content* not)) @@ -745,7 +749,7 @@ (when show-content? [:div {:class (stl/css :content :content-interactions)} - (for [[index interaction] (d/enumerate interactions)] + (for [{:keys [::d/index] :as interaction} interactions] [:> interaction-item* {:key (str (:id shape) "-" index) :index index :shape shape