Refactor ObjectDisplay component to enhance ID validation and handling. Introduced utility functions for ID extraction and validation, ensuring proper object hydration and subscription management. Updated related logic to improve robustness against invalid IDs.

This commit is contained in:
Tom Butcher 2026-03-06 23:43:15 +00:00
parent 62f0e047e2
commit 545cc0c526

View File

@ -31,12 +31,33 @@ const ObjectDisplay = ({
setObjectData((prev) => merge({}, prev, value))
}, [])
// Detect minimal objects that only contain an _id
// Ensure ID is valid before hydrate/subscribe (non-empty, not null/undefined)
const isValidId = useCallback((id) => {
return id != null && String(id).trim() !== ''
}, [])
// Extract string ID from object; handles both primitive _id and populated ref (_id as object)
const getStringId = useCallback((obj) => {
const id = obj?._id
if (id == null) return null
if (typeof id === 'string') return id
if (typeof id === 'object' && id !== null && typeof id._id === 'string')
return id._id
return null
}, [])
// Detect minimal objects that only contain an _id (must be string, not populated object)
const isMinimalObject = useCallback((obj) => {
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false
const keys = Object.keys(obj)
return keys.length === 1 && keys[0] === '_id' && obj._id
}, [])
const id = obj._id
return (
keys.length === 1 &&
keys[0] === '_id' &&
typeof id === 'string' &&
isValidId(id)
)
}, [isValidId])
// If only an _id is provided, fetch the full object via spotlight
const fetchFullObjectIfNeeded = useCallback(
@ -64,9 +85,10 @@ const ObjectDisplay = ({
// Subscribe to object updates when component mounts
useEffect(() => {
if (object?._id && objectType && connected && token != null) {
const id = getStringId(object)
if (isValidId(id) && objectType && connected && token != null) {
const objectUpdatesUnsubscribe = subscribeToObjectUpdates(
object._id,
id,
objectType,
updateObjectEventHandler
)
@ -76,27 +98,31 @@ const ObjectDisplay = ({
}
}
}, [
object?._id,
object,
objectType,
subscribeToObjectUpdates,
connected,
token,
updateObjectEventHandler
updateObjectEventHandler,
isValidId,
getStringId
])
// Update local state when object prop changes
useEffect(() => {
if (token == null) return
const id = getStringId(object)
if (!isValidId(id)) return
const isMinimal = isMinimalObject(object)
// Only skip re-fetch when we have a minimal object and already hydrated this id
if (isMinimal && idRef.current === object?._id) return
if (isMinimal && idRef.current === id) return
let cancelled = false
if (isMinimal) setIsHydrating(true)
const hydrateObject = async () => {
const fullObject = await fetchFullObjectIfNeeded(object)
if (!cancelled) {
setObjectData((prev) => merge({}, prev, fullObject))
if (isMinimal) idRef.current = object?._id
if (isMinimal) idRef.current = id
setIsHydrating(false)
}
}
@ -105,7 +131,7 @@ const ObjectDisplay = ({
cancelled = true
setIsHydrating(false)
}
}, [object, fetchFullObjectIfNeeded, isMinimalObject, token])
}, [object, fetchFullObjectIfNeeded, isMinimalObject, isValidId, getStringId, token])
if (!objectData) {
return <Text type='secondary'>n/a</Text>
}
@ -137,7 +163,7 @@ const ObjectDisplay = ({
var hyperlink = null
const defaultModelActions =
model.actions?.filter((action) => action.default == true) || []
const objectId = objectData._id
const objectId = getStringId(objectData)
if (defaultModelActions.length >= 1 && objectId) {
hyperlink = defaultModelActions[0].url(objectId)
@ -229,9 +255,9 @@ const ObjectDisplay = ({
<div style={{ minWidth: 0 }}>
{renderNameDisplay()}
{objectData?._id && !objectData?.name ? (
{objectId && !objectData?.name ? (
<IdDisplay
id={objectData?._id}
id={objectId}
reference={objectData?._reference || undefined}
type={objectType}
longId={false}