From 8173cfe9e3b805e8ae92cdb57a73e8414abb773f Mon Sep 17 00:00:00 2001 From: Mehmet Salih Yavuz Date: Tue, 5 May 2026 17:52:36 +0300 Subject: [PATCH] fix(CollectionControl): assign stable ids to keyless items (#39862) --- .../controls/CollectionControl/index.tsx | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/superset-frontend/src/explore/components/controls/CollectionControl/index.tsx b/superset-frontend/src/explore/components/controls/CollectionControl/index.tsx index 0e33183e753..0ad904731fa 100644 --- a/superset-frontend/src/explore/components/controls/CollectionControl/index.tsx +++ b/superset-frontend/src/explore/components/controls/CollectionControl/index.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useRef } from 'react'; import { IconTooltip, List } from '@superset-ui/core/components'; import { nanoid } from 'nanoid'; import { t } from '@apache-superset/core/translation'; @@ -185,8 +185,22 @@ function CollectionControl({ }), ); + // Two items can collide when keyAccessor returns falsy and the index + // fallback is used — breaking dnd-kit reordering and React reconciliation. + // Assign a stable nanoid per item ref when no key is available. + const generatedIdsRef = useRef>(new WeakMap()); const itemIds = useMemo( - () => value.map((item, i) => keyAccessor(item) || String(i)), + () => + value.map(item => { + const accessed = keyAccessor(item); + if (accessed) return accessed; + let id = generatedIdsRef.current.get(item); + if (!id) { + id = nanoid(11); + generatedIdsRef.current.set(item, id); + } + return id; + }), [value, keyAccessor], ); @@ -197,8 +211,16 @@ function CollectionControl({ const onChangeItem = useCallback( (i: number, itemValue: CollectionItem) => { + const oldItem = value[i]; + const newItem = { ...oldItem, ...itemValue }; + // Replacing the object would orphan the WeakMap-stored id and remount + // the row. Carry the generated id over to the new ref. + const generatedId = generatedIdsRef.current.get(oldItem); + if (generatedId) { + generatedIdsRef.current.set(newItem, generatedId); + } const newValue = [...value]; - newValue[i] = { ...value[i], ...itemValue }; + newValue[i] = newItem; onChange?.(newValue); }, [value, onChange],