Skip to content

Commit 1a3aeb8

Browse files
authored
fix: folder creation errors when collection uses translation function labels (#15216)
### What Fixes the "Functions cannot be passed directly to Client Components" error that occurs when clicking the create folder button if any collection has translation function labels (e.g., `singular: ({ t }) => t('authentication:account')`). ### Why The `folderType` select field was passing options through `clientProps`, which gets directly serialized when sent to the client component. When collection labels used translation functions, these functions were included in the serialized data, causing React to throw an error since functions can't be serialized across the server/client boundary. ### How Moved the `folderType` field options from `clientProps` to the field definition itself (`field.options`), and updated `FolderTypeField` to read options from the field config. Fixes #15199
1 parent 454042e commit 1a3aeb8

6 files changed

Lines changed: 90 additions & 12 deletions

File tree

packages/payload/src/folders/createFolderCollection.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,6 @@ export const createFolderCollection = ({
8686
admin: {
8787
components: {
8888
Field: {
89-
clientProps: {
90-
options: collectionOptions,
91-
},
9289
path: '@payloadcms/ui#FolderTypeField',
9390
},
9491
},

packages/ui/src/elements/FolderView/FolderTypeField/index.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ import { useField } from '../../../forms/useField/index.js'
1111
import { useFolder } from '../../../providers/Folders/index.js'
1212
import { useTranslation } from '../../../providers/Translation/index.js'
1313

14-
export const FolderTypeField = ({
15-
options: allSelectOptions,
16-
...props
17-
}: { options: Option[] } & SelectFieldClientProps) => {
14+
export const FolderTypeField = (props: SelectFieldClientProps) => {
1815
const {
1916
field,
2017
field: {
@@ -28,6 +25,7 @@ export const FolderTypeField = ({
2825
hasMany = false,
2926
label,
3027
localized,
28+
options: allSelectOptions = [],
3129
required,
3230
},
3331
onChange: onChangeFromProps,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
export const TranslatedLabels: CollectionConfig = {
4+
slug: 'translated-labels',
5+
admin: {
6+
useAsTitle: 'title',
7+
},
8+
fields: [
9+
{
10+
name: 'title',
11+
type: 'text',
12+
},
13+
],
14+
folders: true,
15+
labels: {
16+
plural: ({ t }) => t('general:documents'),
17+
singular: ({ t }) => t('general:document'),
18+
},
19+
}

test/folders/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Drafts } from './collections/Drafts/index.js'
99
import { Media } from './collections/Media/index.js'
1010
import { OmittedFromBrowseBy } from './collections/OmittedFromBrowseBy/index.js'
1111
import { Posts } from './collections/Posts/index.js'
12+
import { TranslatedLabels } from './collections/TranslatedLabels/index.js'
1213
// import { seed } from './seed/index.js'
1314

1415
export default buildConfigWithDefaults({
@@ -29,7 +30,7 @@ export default buildConfigWithDefaults({
2930
},
3031
],
3132
},
32-
collections: [Posts, Media, Drafts, Autosave, OmittedFromBrowseBy],
33+
collections: [Posts, Media, Drafts, Autosave, OmittedFromBrowseBy, TranslatedLabels],
3334
globals: [
3435
{
3536
slug: 'global',

test/folders/e2e.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,31 @@ test.describe('Folders', () => {
9999
await folderPill.click()
100100
await createFolderFromDoc({ folderName: 'New Folder From Doc', page })
101101
})
102+
103+
test('should create folder with collection that has translation function labels', async () => {
104+
await page.goto(formatAdminURL({ adminRoute, path: '/browse-by-folder', serverURL }))
105+
106+
const createButton = page
107+
.locator('.list-header__title-and-actions .create-new-doc-in-folder__button')
108+
.filter({ hasText: 'Create folder' })
109+
await expect(createButton).toBeVisible()
110+
await createButton.click()
111+
112+
// The folder drawer should open successfully without React serialization errors
113+
const drawer = page.locator('dialog .collection-edit--payload-folders')
114+
await expect(drawer).toBeVisible()
115+
116+
const selectLocator = drawer.locator('#field-folderType')
117+
await expect(selectLocator).toBeVisible()
118+
119+
// Should be able to open the select menu without errors
120+
await openSelectMenu({ selectLocator })
121+
122+
const translatedLabelsOption = page.locator('.rs__option', {
123+
hasText: 'Documents',
124+
})
125+
await expect(translatedLabelsOption).toBeVisible()
126+
})
102127
})
103128

104129
test.describe('Folder view actions', () => {

test/folders/payload-types.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export interface Config {
7272
drafts: Draft;
7373
autosave: Autosave;
7474
'omitted-from-browse-by': OmittedFromBrowseBy;
75+
'translated-labels': TranslatedLabel;
7576
'payload-kv': PayloadKv;
7677
users: User;
7778
'payload-folders': FolderInterface;
@@ -81,7 +82,14 @@ export interface Config {
8182
};
8283
collectionsJoins: {
8384
'payload-folders': {
84-
documentsAndFolders: 'payload-folders' | 'posts' | 'media' | 'drafts' | 'autosave' | 'omitted-from-browse-by';
85+
documentsAndFolders:
86+
| 'payload-folders'
87+
| 'posts'
88+
| 'media'
89+
| 'drafts'
90+
| 'autosave'
91+
| 'omitted-from-browse-by'
92+
| 'translated-labels';
8593
};
8694
};
8795
collectionsSelect: {
@@ -90,6 +98,7 @@ export interface Config {
9098
drafts: DraftsSelect<false> | DraftsSelect<true>;
9199
autosave: AutosaveSelect<false> | AutosaveSelect<true>;
92100
'omitted-from-browse-by': OmittedFromBrowseBySelect<false> | OmittedFromBrowseBySelect<true>;
101+
'translated-labels': TranslatedLabelsSelect<false> | TranslatedLabelsSelect<true>;
93102
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
94103
users: UsersSelect<false> | UsersSelect<true>;
95104
'payload-folders': PayloadFoldersSelect<false> | PayloadFoldersSelect<true>;
@@ -202,11 +211,15 @@ export interface FolderInterface {
202211
relationTo?: 'omitted-from-browse-by';
203212
value: string | OmittedFromBrowseBy;
204213
}
214+
| {
215+
relationTo?: 'translated-labels';
216+
value: string | TranslatedLabel;
217+
}
205218
)[];
206219
hasNextPage?: boolean;
207220
totalDocs?: number;
208221
};
209-
folderType?: ('posts' | 'media' | 'drafts' | 'autosave' | 'omitted-from-browse-by')[] | null;
222+
folderType?: ('posts' | 'media' | 'drafts' | 'autosave' | 'omitted-from-browse-by' | 'translated-labels')[] | null;
210223
folderSlug?: string | null;
211224
updatedAt: string;
212225
createdAt: string;
@@ -246,6 +259,17 @@ export interface OmittedFromBrowseBy {
246259
updatedAt: string;
247260
createdAt: string;
248261
}
262+
/**
263+
* This interface was referenced by `Config`'s JSON-Schema
264+
* via the `definition` "translated-labels".
265+
*/
266+
export interface TranslatedLabel {
267+
id: string;
268+
title?: string | null;
269+
folder?: (string | null) | FolderInterface;
270+
updatedAt: string;
271+
createdAt: string;
272+
}
249273
/**
250274
* This interface was referenced by `Config`'s JSON-Schema
251275
* via the `definition` "payload-kv".
@@ -314,6 +338,10 @@ export interface PayloadLockedDocument {
314338
relationTo: 'omitted-from-browse-by';
315339
value: string | OmittedFromBrowseBy;
316340
} | null)
341+
| ({
342+
relationTo: 'translated-labels';
343+
value: string | TranslatedLabel;
344+
} | null)
317345
| ({
318346
relationTo: 'users';
319347
value: string | User;
@@ -428,6 +456,16 @@ export interface OmittedFromBrowseBySelect<T extends boolean = true> {
428456
updatedAt?: T;
429457
createdAt?: T;
430458
}
459+
/**
460+
* This interface was referenced by `Config`'s JSON-Schema
461+
* via the `definition` "translated-labels_select".
462+
*/
463+
export interface TranslatedLabelsSelect<T extends boolean = true> {
464+
title?: T;
465+
folder?: T;
466+
updatedAt?: T;
467+
createdAt?: T;
468+
}
431469
/**
432470
* This interface was referenced by `Config`'s JSON-Schema
433471
* via the `definition` "payload-kv_select".
@@ -533,6 +571,6 @@ export interface Auth {
533571

534572

535573
declare module 'payload' {
536-
// @ts-ignore
574+
// @ts-ignore
537575
export interface GeneratedTypes extends Config {}
538-
}
576+
}

0 commit comments

Comments
 (0)