fix(ui): ensure file field is only serialized at top-level for upload-enabled collections (#12074)
This fixes an issue where fields with the name `file` was being serialized as a top-level field in multipart form data even when the collection was not upload-enabled. This caused the value of `file` (when used as a regular field like a text, array, etc.) to be stripped from the `_payload`. - Updated `createFormData` to only delete `data.file` and serialize it at the top level if `docConfig.upload` is defined. - This prevents unintended loss of `file` field values for non-upload collections. The `file` field now remains safely nested in `_payload` unless it's part of an upload-enabled collection.
This commit is contained in:
@@ -56,7 +56,8 @@ import { initContextState } from './initContextState.js'
|
|||||||
const baseClass = 'form'
|
const baseClass = 'form'
|
||||||
|
|
||||||
export const Form: React.FC<FormProps> = (props) => {
|
export const Form: React.FC<FormProps> = (props) => {
|
||||||
const { id, collectionSlug, docPermissions, getDocPreferences, globalSlug } = useDocumentInfo()
|
const { id, collectionSlug, docConfig, docPermissions, getDocPreferences, globalSlug } =
|
||||||
|
useDocumentInfo()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
action,
|
action,
|
||||||
@@ -491,8 +492,28 @@ export const Form: React.FC<FormProps> = (props) => {
|
|||||||
|
|
||||||
let file = data?.file
|
let file = data?.file
|
||||||
|
|
||||||
if (file) {
|
if (docConfig && 'upload' in docConfig && docConfig.upload && file) {
|
||||||
delete data.file
|
delete data.file
|
||||||
|
|
||||||
|
const handler = getUploadHandler({ collectionSlug })
|
||||||
|
|
||||||
|
if (typeof handler === 'function') {
|
||||||
|
let filename = file.name
|
||||||
|
const clientUploadContext = await handler({
|
||||||
|
file,
|
||||||
|
updateFilename: (value) => {
|
||||||
|
filename = value
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
file = JSON.stringify({
|
||||||
|
clientUploadContext,
|
||||||
|
collectionSlug,
|
||||||
|
filename,
|
||||||
|
mimeType: file.type,
|
||||||
|
size: file.size,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mergeOverrideData) {
|
if (mergeOverrideData) {
|
||||||
@@ -504,37 +525,23 @@ export const Form: React.FC<FormProps> = (props) => {
|
|||||||
data = overrides
|
data = overrides
|
||||||
}
|
}
|
||||||
|
|
||||||
const handler = getUploadHandler({ collectionSlug })
|
const dataToSerialize: Record<string, unknown> = {
|
||||||
|
_payload: JSON.stringify(data),
|
||||||
if (file && typeof handler === 'function') {
|
|
||||||
let filename = file.name
|
|
||||||
const clientUploadContext = await handler({
|
|
||||||
file,
|
|
||||||
updateFilename: (value) => {
|
|
||||||
filename = value
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
file = JSON.stringify({
|
|
||||||
clientUploadContext,
|
|
||||||
collectionSlug,
|
|
||||||
filename,
|
|
||||||
mimeType: file.type,
|
|
||||||
size: file.size,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataToSerialize = {
|
if (docConfig && 'upload' in docConfig && docConfig.upload && file) {
|
||||||
_payload: JSON.stringify(data),
|
dataToSerialize.file = file
|
||||||
file,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nullAsUndefineds is important to allow uploads and relationship fields to clear themselves
|
// nullAsUndefineds is important to allow uploads and relationship fields to clear themselves
|
||||||
const formData = serialize(dataToSerialize, { indices: true, nullsAsUndefineds: false })
|
const formData = serialize(dataToSerialize, {
|
||||||
|
indices: true,
|
||||||
|
nullsAsUndefineds: false,
|
||||||
|
})
|
||||||
|
|
||||||
return formData
|
return formData
|
||||||
},
|
},
|
||||||
[collectionSlug, getUploadHandler],
|
[collectionSlug, docConfig, getUploadHandler],
|
||||||
)
|
)
|
||||||
|
|
||||||
const reset = useCallback(
|
const reset = useCallback(
|
||||||
|
|||||||
@@ -236,6 +236,10 @@ export const Posts: CollectionConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'file',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
labels: {
|
labels: {
|
||||||
plural: slugPluralLabel,
|
plural: slugPluralLabel,
|
||||||
|
|||||||
@@ -512,6 +512,17 @@ describe('Document View', () => {
|
|||||||
await expect(publishButton).toContainText('Publish in English')
|
await expect(publishButton).toContainText('Publish in English')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('reserved field names', () => {
|
||||||
|
test('should allow creation of field named file in non-upload enabled collection', async () => {
|
||||||
|
await page.goto(postsUrl.create)
|
||||||
|
const fileField = page.locator('#field-file')
|
||||||
|
await fileField.fill('some file text')
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
|
||||||
|
await expect(fileField).toHaveValue('some file text')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
async function createPost(overrides?: Partial<Post>): Promise<Post> {
|
async function createPost(overrides?: Partial<Post>): Promise<Post> {
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ export interface Post {
|
|||||||
sidebarField?: string | null;
|
sidebarField?: string | null;
|
||||||
wavelengths?: ('fm' | 'am') | null;
|
wavelengths?: ('fm' | 'am') | null;
|
||||||
selectField?: ('option1' | 'option2')[] | null;
|
selectField?: ('option1' | 'option2')[] | null;
|
||||||
|
file?: string | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
_status?: ('draft' | 'published') | null;
|
_status?: ('draft' | 'published') | null;
|
||||||
@@ -665,6 +666,7 @@ export interface PostsSelect<T extends boolean = true> {
|
|||||||
sidebarField?: T;
|
sidebarField?: T;
|
||||||
wavelengths?: T;
|
wavelengths?: T;
|
||||||
selectField?: T;
|
selectField?: T;
|
||||||
|
file?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
_status?: T;
|
_status?: T;
|
||||||
|
|||||||
Reference in New Issue
Block a user