Skip to content

Commit fd44d2b

Browse files
authored
fix(ui): error on inserting new documents on uploads with hasMany (#14567)
Fixes #14563 Issue was that the values were not being normalised and so inserting into a fresh document it inserted the array as expected but inserting into a field with existing data it resulted in malformed values.
1 parent 8b34e40 commit fd44d2b

File tree

8 files changed

+690
-37
lines changed

8 files changed

+690
-37
lines changed

packages/ui/src/fields/Upload/Input.tsx

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { FieldLabel } from '../../fields/FieldLabel/index.js'
3333
import { useAuth } from '../../providers/Auth/index.js'
3434
import { useLocale } from '../../providers/Locale/index.js'
3535
import { useTranslation } from '../../providers/Translation/index.js'
36+
import { normalizeRelationshipValue } from '../../utilities/normalizeRelationshipValue.js'
3637
import { fieldBaseClass } from '../shared/index.js'
3738
import { UploadComponentHasMany } from './HasMany/index.js'
3839
import { UploadComponentHasOne } from './HasOne/index.js'
@@ -249,7 +250,14 @@ export function UploadInput(props: UploadInputProps) {
249250
if (!grouped[relationTo]) {
250251
grouped[relationTo] = []
251252
}
252-
grouped[relationTo].push(value)
253+
// Ensure we extract the actual ID value, not an object
254+
let idValue: number | string = value
255+
256+
if (value && typeof value === 'object' && 'value' in (value as any)) {
257+
idValue = (value as any).value
258+
}
259+
260+
grouped[relationTo].push(idValue)
253261
})
254262

255263
// 2. Fetch per collection
@@ -315,6 +323,11 @@ export function UploadInput(props: UploadInputProps) {
315323
[serverURL, api, code, i18n.language, t],
316324
)
317325

326+
const normalizeValue = useCallback(
327+
(value: any): any => normalizeRelationshipValue(value, relationTo),
328+
[relationTo],
329+
)
330+
318331
const onUploadSuccess: BulkUploadContext['onSuccess'] = useCallback(
319332
(uploadedForms) => {
320333
const isPoly = Array.isArray(relationTo)
@@ -323,7 +336,9 @@ export function UploadInput(props: UploadInputProps) {
323336
const newValues = uploadedForms.map((form) =>
324337
isPoly ? { relationTo: form.collectionSlug, value: form.doc.id } : form.doc.id,
325338
)
326-
const mergedValue = [...(Array.isArray(value) ? value : []), ...newValues]
339+
// Normalize existing values before merging
340+
const normalizedExisting = Array.isArray(value) ? value.map(normalizeValue) : []
341+
const mergedValue = [...normalizedExisting, ...newValues]
327342
onChange(mergedValue)
328343
setPopulatedDocs((currentDocs) => [
329344
...(currentDocs || []),
@@ -346,7 +361,7 @@ export function UploadInput(props: UploadInputProps) {
346361
])
347362
}
348363
},
349-
[value, onChange, hasMany, relationTo],
364+
[value, onChange, hasMany, relationTo, normalizeValue],
350365
)
351366

352367
const onLocalFileSelection = React.useCallback(
@@ -405,6 +420,7 @@ export function UploadInput(props: UploadInputProps) {
405420
relationTo: activeRelationTo,
406421
value: id,
407422
}))
423+
408424
const loadedDocs = await populateDocs(itemsToLoad)
409425
if (loadedDocs) {
410426
setPopulatedDocs((currentDocs) => [...(currentDocs || []), ...loadedDocs])
@@ -413,10 +429,12 @@ export function UploadInput(props: UploadInputProps) {
413429
const newValues = selectedDocIDs.map((id) =>
414430
isPoly ? { relationTo: activeRelationTo, value: id } : id,
415431
)
416-
onChange([...(Array.isArray(value) ? value : []), ...newValues])
432+
// Normalize existing values before merging
433+
const normalizedExisting = Array.isArray(value) ? value.map(normalizeValue) : []
434+
onChange([...normalizedExisting, ...newValues])
417435
closeListDrawer()
418436
},
419-
[activeRelationTo, closeListDrawer, onChange, populateDocs, value, relationTo],
437+
[activeRelationTo, closeListDrawer, onChange, populateDocs, value, relationTo, normalizeValue],
420438
)
421439

422440
const onDocCreate = React.useCallback(
@@ -456,18 +474,19 @@ export function UploadInput(props: UploadInputProps) {
456474
return currentDocs
457475
})
458476

459-
const newValue = isPoly ? { relationTo: collectionSlug, value: doc.id } : doc.id
460-
461477
if (hasMany) {
462-
const valueToUse = [...(Array.isArray(value) ? value : []), newValue]
478+
const newValue = isPoly ? { relationTo: collectionSlug, value: doc.id } : doc.id
479+
// Normalize existing values before merging
480+
const normalizedExisting = Array.isArray(value) ? value.map(normalizeValue) : []
481+
const valueToUse = [...normalizedExisting, newValue]
463482
onChange(valueToUse)
464483
} else {
465484
const valueToUse = isPoly ? { relationTo: collectionSlug, value: doc.id } : doc.id
466485
onChange(valueToUse)
467486
}
468487
closeListDrawer()
469488
},
470-
[closeListDrawer, hasMany, populateDocs, onChange, value, relationTo],
489+
[closeListDrawer, hasMany, populateDocs, onChange, value, relationTo, normalizeValue],
471490
)
472491

473492
const reloadDoc = React.useCallback<ReloadDoc>(
@@ -534,7 +553,7 @@ export function UploadInput(props: UploadInputProps) {
534553
useEffect(() => {
535554
async function loadInitialDocs() {
536555
if (value) {
537-
let itemsToLoad: ValueWithRelation[]
556+
let itemsToLoad: ValueWithRelation[] = []
538557
if (
539558
Array.isArray(relationTo) &&
540559
((typeof value === 'object' && 'relationTo' in value) ||
@@ -545,29 +564,55 @@ export function UploadInput(props: UploadInputProps) {
545564
) {
546565
// For poly uploads, value should already be in the format { relationTo, value }
547566
const values = Array.isArray(value) ? value : [value]
548-
itemsToLoad = values.filter(
549-
(v): v is ValueWithRelation => typeof v === 'object' && 'relationTo' in v,
550-
)
567+
itemsToLoad = values
568+
.filter((v): v is ValueWithRelation => typeof v === 'object' && 'relationTo' in v)
569+
.map((v) => {
570+
// Ensure the value property is a simple ID, not nested
571+
let idValue: any = v.value
572+
while (
573+
idValue &&
574+
typeof idValue === 'object' &&
575+
idValue !== null &&
576+
'value' in idValue
577+
) {
578+
idValue = idValue.value
579+
}
580+
return {
581+
relationTo: v.relationTo,
582+
value: idValue as number | string,
583+
}
584+
})
551585
} else {
552586
// This check is here to satisfy TypeScript that relationTo is a string
553587
if (!Array.isArray(relationTo)) {
554588
// For single collection uploads, we need to wrap the IDs
555589
const ids = Array.isArray(value) ? value : [value]
556590
itemsToLoad = ids.map((id): ValueWithRelation => {
557-
const idValue = typeof id === 'object' && 'value' in id ? id.value : id
591+
// Extract the actual ID, handling nested objects
592+
let idValue: any = id
593+
while (
594+
idValue &&
595+
typeof idValue === 'object' &&
596+
idValue !== null &&
597+
'value' in idValue
598+
) {
599+
idValue = idValue.value
600+
}
558601
return {
559602
relationTo,
560-
value: idValue,
603+
value: idValue as number | string,
561604
}
562605
})
563606
}
564607
}
565608

566-
const loadedDocs = await populateDocs(itemsToLoad)
609+
if (itemsToLoad.length > 0) {
610+
const loadedDocs = await populateDocs(itemsToLoad)
567611

568-
if (loadedDocs) {
569-
setPopulatedDocs(loadedDocs)
570-
loadedValueRef.current = value
612+
if (loadedDocs) {
613+
setPopulatedDocs(loadedDocs)
614+
loadedValueRef.current = value
615+
}
571616
}
572617
} else {
573618
// Clear populated docs when value is cleared

0 commit comments

Comments
 (0)