Enhance ObjectSelect Component with Improved State Management and Value Handling
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
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:
parent
da2881d505
commit
968dc0667a
@ -251,6 +251,8 @@ const ObjectSelect = ({
|
|||||||
const masterFilterRef = useRef(masterFilter)
|
const masterFilterRef = useRef(masterFilter)
|
||||||
const clearedMissingValueRef = useRef(false)
|
const clearedMissingValueRef = useRef(false)
|
||||||
const loadGenerationRef = useRef(0)
|
const loadGenerationRef = useRef(0)
|
||||||
|
const loadedKeysRef = useRef(new Set())
|
||||||
|
const treeSelectValueRef = useRef(null)
|
||||||
const getSelectKey = useCallback(
|
const getSelectKey = useCallback(
|
||||||
(selectType, selectMasterFilter) =>
|
(selectType, selectMasterFilter) =>
|
||||||
`${selectType}::${JSON.stringify(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
|
// Normalize a value to an identity string so we can detect in-place _id updates
|
||||||
const getValueIdentity = useCallback((val) => {
|
const getValueIdentity = useCallback((val) => {
|
||||||
if (val && typeof val === 'object') {
|
if (val == null || val === '') return ''
|
||||||
// Handle arrays
|
if (
|
||||||
if (Array.isArray(val)) {
|
typeof val === 'string' ||
|
||||||
const ids = val
|
typeof val === 'number' ||
|
||||||
.map((item) => {
|
typeof val === 'boolean'
|
||||||
if (item && typeof item === 'object') {
|
) {
|
||||||
if (item._id) return String(item._id)
|
return String(val).toLowerCase()
|
||||||
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 (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)
|
return JSON.stringify(val)
|
||||||
}, [])
|
}, [])
|
||||||
const prevValueIdentityRef = useRef(getValueIdentity(value))
|
const prevValueIdentityRef = useRef(getValueIdentity(value))
|
||||||
@ -426,11 +422,7 @@ const ObjectSelect = ({
|
|||||||
masterFilterRef.current
|
masterFilterRef.current
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (itemExists) {
|
silentReloadRef.current?.()
|
||||||
reloadRef.current?.()
|
|
||||||
} else {
|
|
||||||
silentReloadRef.current?.()
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,7 +437,7 @@ const ObjectSelect = ({
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const newEventHandler = useCallback(() => {
|
const newEventHandler = useCallback(() => {
|
||||||
reloadRef.current?.()
|
silentReloadRef.current?.()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const reloadRef = useRef(null)
|
const reloadRef = useRef(null)
|
||||||
@ -536,8 +528,8 @@ const ObjectSelect = ({
|
|||||||
newFilterPath,
|
newFilterPath,
|
||||||
objects
|
objects
|
||||||
)
|
)
|
||||||
const resolvedChildren =
|
const loaded =
|
||||||
nodeChildren.length === 0 ? undefined : nodeChildren
|
nodeChildren.length > 0 || loadedKeysRef.current.has(nodeKey)
|
||||||
const modelProperty = getModelProperty(type, property)
|
const modelProperty = getModelProperty(type, property)
|
||||||
return {
|
return {
|
||||||
title: <ObjectProperty {...modelProperty} value={value} />,
|
title: <ObjectProperty {...modelProperty} value={value} />,
|
||||||
@ -549,7 +541,8 @@ const ObjectSelect = ({
|
|||||||
filterPath: newFilterPath,
|
filterPath: newFilterPath,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
children: resolvedChildren
|
loaded,
|
||||||
|
children: loaded ? nodeChildren : undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@ -608,6 +601,7 @@ const ObjectSelect = ({
|
|||||||
async (node) => {
|
async (node) => {
|
||||||
if (!node.property) return
|
if (!node.property) return
|
||||||
if (type == 'unknown') return
|
if (type == 'unknown') return
|
||||||
|
if (node.key) loadedKeysRef.current.add(node.key)
|
||||||
await handleFetchObjectsProperties(buildFilterFromNode(node))
|
await handleFetchObjectsProperties(buildFilterFromNode(node))
|
||||||
},
|
},
|
||||||
[buildFilterFromNode, handleFetchObjectsProperties, type]
|
[buildFilterFromNode, handleFetchObjectsProperties, type]
|
||||||
@ -696,6 +690,13 @@ const ObjectSelect = ({
|
|||||||
|
|
||||||
const onTreeSelectChange = useCallback(
|
const onTreeSelectChange = useCallback(
|
||||||
(nextValue) => {
|
(nextValue) => {
|
||||||
|
if (
|
||||||
|
getValueIdentity(nextValue) ===
|
||||||
|
getValueIdentity(treeSelectValueRef.current)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const isEmptySelection = multiple
|
const isEmptySelection = multiple
|
||||||
? !Array.isArray(nextValue) || nextValue.length === 0
|
? !Array.isArray(nextValue) || nextValue.length === 0
|
||||||
: nextValue == null || nextValue === ''
|
: nextValue == null || nextValue === ''
|
||||||
@ -744,7 +745,7 @@ const ObjectSelect = ({
|
|||||||
}
|
}
|
||||||
onChange?.(null)
|
onChange?.(null)
|
||||||
},
|
},
|
||||||
[multiple, onChange, findObjectById]
|
[multiple, onChange, findObjectById, getValueIdentity]
|
||||||
)
|
)
|
||||||
|
|
||||||
const onSearch = useCallback(
|
const onSearch = useCallback(
|
||||||
@ -856,6 +857,10 @@ const ObjectSelect = ({
|
|||||||
treeDataRef.current = treeData
|
treeDataRef.current = treeData
|
||||||
}, [treeData])
|
}, [treeData])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
treeSelectValueRef.current = treeSelectValue
|
||||||
|
}, [treeSelectValue])
|
||||||
|
|
||||||
const prevValuesRef = useRef({ type, masterFilter })
|
const prevValuesRef = useRef({ type, masterFilter })
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -877,6 +882,7 @@ const ObjectSelect = ({
|
|||||||
treeDataRef.current = []
|
treeDataRef.current = []
|
||||||
setTreeVersion((v) => v + 1)
|
setTreeVersion((v) => v + 1)
|
||||||
setExpandedKeys([])
|
setExpandedKeys([])
|
||||||
|
loadedKeysRef.current = new Set()
|
||||||
setInitialized(false)
|
setInitialized(false)
|
||||||
valueRef.current = null
|
valueRef.current = null
|
||||||
setTreeSelectValue(null)
|
setTreeSelectValue(null)
|
||||||
@ -899,13 +905,16 @@ const ObjectSelect = ({
|
|||||||
const changeSource = isInternalChangeRef.current ? 'internal' : 'external'
|
const changeSource = isInternalChangeRef.current ? 'internal' : 'external'
|
||||||
|
|
||||||
if (changeSource == 'external') {
|
if (changeSource == 'external') {
|
||||||
loadGenerationRef.current += 1
|
const nextId = getValueId(value)
|
||||||
setObjectPropertiesTree({})
|
const alreadyInTree = isValueInTree(treeDataRef.current, nextId)
|
||||||
setTreeData([])
|
const alreadySelected =
|
||||||
treeDataRef.current = []
|
treeSelectValueRef.current != null &&
|
||||||
setInitialized(false)
|
getValueIdentity(treeSelectValueRef.current) === currentValueIdentity
|
||||||
setInitialLoading(true)
|
|
||||||
valueRef.current = null
|
if (!alreadyInTree && !alreadySelected) {
|
||||||
|
loadGenerationRef.current += 1
|
||||||
|
}
|
||||||
|
|
||||||
clearedMissingValueRef.current = false
|
clearedMissingValueRef.current = false
|
||||||
setValueNotFound(false)
|
setValueNotFound(false)
|
||||||
}
|
}
|
||||||
@ -924,6 +933,40 @@ const ObjectSelect = ({
|
|||||||
const generation = loadGenerationRef.current
|
const generation = loadGenerationRef.current
|
||||||
const handleValue = async () => {
|
const handleValue = async () => {
|
||||||
if (generation !== loadGenerationRef.current) return
|
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 (
|
if (
|
||||||
multiple &&
|
multiple &&
|
||||||
Array.isArray(value) &&
|
Array.isArray(value) &&
|
||||||
@ -975,6 +1018,7 @@ const ObjectSelect = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setExpandedKeys([...new Set(pathKeys)])
|
setExpandedKeys([...new Set(pathKeys)])
|
||||||
|
pathKeys.forEach((key) => loadedKeysRef.current.add(key))
|
||||||
setTreeSelectValue(
|
setTreeSelectValue(
|
||||||
value
|
value
|
||||||
.map((item) => toSelectValue(getValueId(item)))
|
.map((item) => toSelectValue(getValueId(item)))
|
||||||
@ -1025,6 +1069,7 @@ const ObjectSelect = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setExpandedKeys(pathKeys)
|
setExpandedKeys(pathKeys)
|
||||||
|
pathKeys.forEach((key) => loadedKeysRef.current.add(key))
|
||||||
const data = await handleFetchObjectsProperties(valueFilter)
|
const data = await handleFetchObjectsProperties(valueFilter)
|
||||||
if (generation !== loadGenerationRef.current) return
|
if (generation !== loadGenerationRef.current) return
|
||||||
if (Array.isArray(data)) applyTreeFromData(data)
|
if (Array.isArray(data)) applyTreeFromData(data)
|
||||||
@ -1069,7 +1114,8 @@ const ObjectSelect = ({
|
|||||||
connected,
|
connected,
|
||||||
getValueIdentity,
|
getValueIdentity,
|
||||||
multiple,
|
multiple,
|
||||||
applyTreeFromData
|
applyTreeFromData,
|
||||||
|
findObjectById
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user