From 968dc0667a79c9274c08259c512fe7c5ff8853d8 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Tue, 15 Sep 2026 01:56:57 +0100 Subject: [PATCH] Enhance ObjectSelect Component with Improved State Management and Value Handling - Introduced refs for loaded keys and tree select value to optimize state management and prevent unnecessary re-renders. - Refactored getValueIdentity function to handle various data types more effectively, including null and boolean values. - Updated selection handling logic to ensure accurate comparisons and prevent redundant updates during value changes. - Enhanced loading logic to track loaded nodes and improve the rendering of child components based on selection state. - Implemented additional useEffect hooks to synchronize state changes with component updates, enhancing overall responsiveness. --- .../Dashboard/common/ObjectSelect.jsx | 130 ++++++++++++------ 1 file changed, 88 insertions(+), 42 deletions(-) diff --git a/src/components/Dashboard/common/ObjectSelect.jsx b/src/components/Dashboard/common/ObjectSelect.jsx index 9446bfc1..316273ab 100644 --- a/src/components/Dashboard/common/ObjectSelect.jsx +++ b/src/components/Dashboard/common/ObjectSelect.jsx @@ -251,6 +251,8 @@ const ObjectSelect = ({ const masterFilterRef = useRef(masterFilter) const clearedMissingValueRef = useRef(false) const loadGenerationRef = useRef(0) + const loadedKeysRef = useRef(new Set()) + const treeSelectValueRef = useRef(null) const getSelectKey = useCallback( (selectType, selectMasterFilter) => `${selectType}::${JSON.stringify(selectMasterFilter ?? {})}`, @@ -269,31 +271,25 @@ const ObjectSelect = ({ // Normalize a value to an identity string so we can detect in-place _id updates const getValueIdentity = useCallback((val) => { - if (val && typeof val === 'object') { - // Handle arrays - if (Array.isArray(val)) { - const ids = val - .map((item) => { - if (item && typeof item === 'object') { - if (item._id) return String(item._id) - if ( - item.value && - typeof item.value === 'object' && - item.value._id - ) - return String(item.value._id) - } - return null - }) - .filter(Boolean) - .sort() - return ids.length > 0 ? ids.join(',') : JSON.stringify(val) - } - // Handle single objects - if (val._id) return String(val._id) - if (val.value && typeof val.value === 'object' && val.value._id) - return String(val.value._id) + if (val == null || val === '') return '' + if ( + typeof val === 'string' || + typeof val === 'number' || + typeof val === 'boolean' + ) { + return String(val).toLowerCase() } + if (typeof val !== 'object') return String(val) + + if (Array.isArray(val)) { + const ids = val + .map((item) => getValueIdentity(item)) + .filter(Boolean) + .sort() + return ids.join(',') + } + if (val._id) return String(val._id).toLowerCase() + if (val.value != null) return getValueIdentity(val.value) return JSON.stringify(val) }, []) const prevValueIdentityRef = useRef(getValueIdentity(value)) @@ -426,11 +422,7 @@ const ObjectSelect = ({ masterFilterRef.current ) ) { - if (itemExists) { - reloadRef.current?.() - } else { - silentReloadRef.current?.() - } + silentReloadRef.current?.() return } @@ -445,7 +437,7 @@ const ObjectSelect = ({ }, []) const newEventHandler = useCallback(() => { - reloadRef.current?.() + silentReloadRef.current?.() }, []) const reloadRef = useRef(null) @@ -536,8 +528,8 @@ const ObjectSelect = ({ newFilterPath, objects ) - const resolvedChildren = - nodeChildren.length === 0 ? undefined : nodeChildren + const loaded = + nodeChildren.length > 0 || loadedKeysRef.current.has(nodeKey) const modelProperty = getModelProperty(type, property) return { title: , @@ -549,7 +541,8 @@ const ObjectSelect = ({ filterPath: newFilterPath, selectable: false, isLeaf: false, - children: resolvedChildren + loaded, + children: loaded ? nodeChildren : undefined } }) .filter(Boolean) @@ -608,6 +601,7 @@ const ObjectSelect = ({ async (node) => { if (!node.property) return if (type == 'unknown') return + if (node.key) loadedKeysRef.current.add(node.key) await handleFetchObjectsProperties(buildFilterFromNode(node)) }, [buildFilterFromNode, handleFetchObjectsProperties, type] @@ -696,6 +690,13 @@ const ObjectSelect = ({ const onTreeSelectChange = useCallback( (nextValue) => { + if ( + getValueIdentity(nextValue) === + getValueIdentity(treeSelectValueRef.current) + ) { + return + } + const isEmptySelection = multiple ? !Array.isArray(nextValue) || nextValue.length === 0 : nextValue == null || nextValue === '' @@ -744,7 +745,7 @@ const ObjectSelect = ({ } onChange?.(null) }, - [multiple, onChange, findObjectById] + [multiple, onChange, findObjectById, getValueIdentity] ) const onSearch = useCallback( @@ -856,6 +857,10 @@ const ObjectSelect = ({ treeDataRef.current = treeData }, [treeData]) + useEffect(() => { + treeSelectValueRef.current = treeSelectValue + }, [treeSelectValue]) + const prevValuesRef = useRef({ type, masterFilter }) useEffect(() => { @@ -877,6 +882,7 @@ const ObjectSelect = ({ treeDataRef.current = [] setTreeVersion((v) => v + 1) setExpandedKeys([]) + loadedKeysRef.current = new Set() setInitialized(false) valueRef.current = null setTreeSelectValue(null) @@ -899,13 +905,16 @@ const ObjectSelect = ({ const changeSource = isInternalChangeRef.current ? 'internal' : 'external' if (changeSource == 'external') { - loadGenerationRef.current += 1 - setObjectPropertiesTree({}) - setTreeData([]) - treeDataRef.current = [] - setInitialized(false) - setInitialLoading(true) - valueRef.current = null + const nextId = getValueId(value) + const alreadyInTree = isValueInTree(treeDataRef.current, nextId) + const alreadySelected = + treeSelectValueRef.current != null && + getValueIdentity(treeSelectValueRef.current) === currentValueIdentity + + if (!alreadyInTree && !alreadySelected) { + loadGenerationRef.current += 1 + } + clearedMissingValueRef.current = false setValueNotFound(false) } @@ -924,6 +933,40 @@ const ObjectSelect = ({ const generation = loadGenerationRef.current const handleValue = async () => { if (generation !== loadGenerationRef.current) return + + const valueIdentity = getValueIdentity(value) + const ids = multiple + ? (Array.isArray(value) ? value.map(getValueId) : []) + : value == null + ? [] + : [getValueId(value)] + const allInTree = + ids.length > 0 && + ids.every( + (id) => + id == null || + id === '' || + isValueInTree(treeDataRef.current, id) || + findObjectById(id) != null + ) + + if ( + value != null && + type != 'unknown' && + allInTree && + getValueIdentity(valueRef.current) !== valueIdentity + ) { + valueRef.current = value + setTreeSelectValue( + multiple + ? ids.map((id) => toSelectValue(id)).filter((id) => id != null) + : toSelectValue(ids[0]) + ) + setInitialized(true) + setInitialLoading(false) + return + } + if ( multiple && Array.isArray(value) && @@ -975,6 +1018,7 @@ const ObjectSelect = ({ } setExpandedKeys([...new Set(pathKeys)]) + pathKeys.forEach((key) => loadedKeysRef.current.add(key)) setTreeSelectValue( value .map((item) => toSelectValue(getValueId(item))) @@ -1025,6 +1069,7 @@ const ObjectSelect = ({ } }) setExpandedKeys(pathKeys) + pathKeys.forEach((key) => loadedKeysRef.current.add(key)) const data = await handleFetchObjectsProperties(valueFilter) if (generation !== loadGenerationRef.current) return if (Array.isArray(data)) applyTreeFromData(data) @@ -1069,7 +1114,8 @@ const ObjectSelect = ({ connected, getValueIdentity, multiple, - applyTreeFromData + applyTreeFromData, + findObjectById ]) useEffect(() => {