Enhance ObjectSelect Component with Improved State Management and Value Handling
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good

- 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.
This commit is contained in:
Tom Butcher 2026-09-15 01:56:57 +01:00
parent da2881d505
commit 968dc0667a

View File

@ -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 (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) => {
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
})
.map((item) => getValueIdentity(item))
.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)
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?.()
}
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: <ObjectProperty {...modelProperty} value={value} />,
@ -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') {
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
setObjectPropertiesTree({})
setTreeData([])
treeDataRef.current = []
setInitialized(false)
setInitialLoading(true)
valueRef.current = null
}
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(() => {