Skip to content

Commit 8b30701

Browse files
authored
feat: passes client field config to server components (#8166)
## Description ### TL;DR: It's currently not possible to render our field components from a server component because their `field` prop is the original field config, not the _client_ config which our components require. Currently, the `field` prop passed into custom fields changes type depending on whether it's a server or client component, leaving server components without any access to the client field config or mechanism to acquire it. This PR passes the client config to all server field components through a new `clientField` prop. This allows the following in a server component, which is very similar to how client field components currently work: Server component: ```tsx import { TextField } from '@payloadcms/ui' import type { TextFieldServerComponent } from 'payload' export const MyCustomServerField: TextFieldServerComponent = ({ clientField }) => { return <TextField field={clientField} /> } ``` Client component: ```tsx 'use client' import { TextField } from '@payloadcms/ui' import type { TextFieldClientComponent } from 'payload' export const MyCustomClientField: TextFieldClientComponent = ({ field }) => { return <TextField field={field} /> } ``` ### Full Background If you have a custom field component, and it's a server component, there is currently no way to pass the field prop into Payload's client-side field components. Here's an example of the problem: ```tsx import { TextField } from '@payloadcms/ui' import type { TextFieldServerComponent } from 'payload' import React from 'react' export const MyServerComponent: TextFieldServerComponent = (props) => { const { field } = props return ( <TextField field={field} /> // This is not possible ) } ``` The config needs to be transformed into a client config, however, because of the sheer number of hard-to-find arguments that the `createClientField` requires, we cannot use it in its raw form. Here is another example of the problem: ```tsx import { TextField } from '@payloadcms/ui' import { createClientField } from '@payloadcms/ui/utilities/createClientField' import type { TextFieldServerComponent } from 'payload' import React from 'react' export const MyServerComponent: TextFieldServerComponent = ({ createClientField }) => { const clientField = createClientField({...}) // Not a good option bc it requires many hard-to-find args return ( <TextField field={clientField} /> ) } ``` Theoretically, we could preformat a `createFieldConfig` function so it can simply be called without arguments: ```tsx import { TextField } from '@payloadcms/ui' import type { TextFieldServerComponent } from 'payload' import React from 'react' export const MyServerComponent: TextFieldServerComponent = ({ createClientField }) => { return <TextField field={createClientField()} /> } ``` But this means the field config would be evaluated twice unnecessarily, including label functions, etc. The right way to fix this is to simply pass the client config to server components through a new `clientField` prop: ```tsx import { TextField } from '@payloadcms/ui' import type { TextFieldServerComponent } from 'payload' import React from 'react' export const MyServerComponent: TextFieldServerComponent = ({ clientField }) => { return <TextField field={clientField} /> } ``` - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## Checklist: - [x] Existing test suite passes locally with my changes - [x] I have made corresponding changes to the documentation
1 parent 9561aa3 commit 8b30701

38 files changed

+477
-194
lines changed

docs/admin/fields.mdx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ All Field Components receive the following props:
136136
| Property | Description |
137137
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
138138
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document.
139-
| **`field`** | The field's config. [More details](#the-field-prop) |
139+
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
140+
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
140141
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
141142
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
142143
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
@@ -189,37 +190,36 @@ import type {
189190

190191
### The `field` Prop
191192

192-
All Field Components are passed their own Field Config through a common `field` prop. Within a Server Component, this is the raw Field Config. Within Client Components, however, the raw Field Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types). Instead, Client Components receives a [Client Config](./components#accessing-the-payload-config), which is a sanitizes version of the Field Config that is safe to pass into Client Components.
193+
All Field Components are passed their own Field Config through a common `field` prop. Within Server Components, this is the original Field Config as written within your Payload Config. Within Client Components, however, this is a "Client Config", which is a sanitized, client-friendly version of the Field Config. This is because the original Field Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types), meaning it cannot be passed into Client Components without first being transformed.
193194

194-
The exact shape of this prop is unique to the specific [Field Type](../fields/overview) being rendered, minus all non-serializable properties. Any [Custom Components](../components) are also resolved into a "mapped component" that is safe to pass.
195+
The Client Field Config is an exact copy of the original Field Config, minus all non-serializable properties, plus all evaluated functions such as field labels, [Custom Components](../components), etc.
196+
197+
Server Component:
195198

196199
```tsx
197200
import React from 'react'
198201
import type { TextFieldServerComponent } from 'payload'
202+
import { TextField } from '@payloadcms/ui'
199203

200-
export const MyServerTextField: TextFieldServerComponent = ({ payload, field: { name } }) => {
201-
const result = await payload.find({
202-
collection: 'myCollection',
203-
depth: 1,
204-
})
205-
206-
// ...
204+
export const MyServerField: TextFieldServerComponent = ({ clientField }) => {
205+
return <TextField field={clientField} />
207206
}
208207
```
209208

209+
<Banner type="info">
210+
<strong>Tip:</strong>
211+
Server Components can still access the original Field Config through the `field` prop.
212+
</Banner>
213+
210214
Client Component:
211215

212216
```tsx
213217
'use client'
214218
import React from 'react'
215219
import type { TextFieldClientComponent } from 'payload'
216220

217-
export const MyClientTextField: TextFieldClientComponent = ({ field: { name } }) => {
218-
return (
219-
<p>
220-
{`This field's name is ${name}`}
221-
</p>
222-
)
221+
export const MyTextField: TextFieldClientComponent = ({ field }) => {
222+
return <TextField field={field} />
223223
}
224224
```
225225

@@ -276,7 +276,8 @@ All Cell Components receive the following props:
276276

277277
| Property | Description |
278278
| ---------------- | ----------------------------------------------------------------- |
279-
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
279+
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
280+
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
280281
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
281282
| **`onClick`** | A function that is called when the cell is clicked. |
282283

@@ -316,7 +317,8 @@ Custom Label Components receive all [Field Component](#the-field-component) prop
316317

317318
| Property | Description |
318319
| -------------- | ---------------------------------------------------------------- |
319-
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
320+
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
321+
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
320322

321323
<Banner type="success">
322324
<strong>Reminder:</strong>
@@ -361,7 +363,8 @@ Custom Error Components receive all [Field Component](#the-field-component) prop
361363

362364
| Property | Description |
363365
| --------------- | ------------------------------------------------------------- |
364-
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
366+
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
367+
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
365368

366369
<Banner type="success">
367370
<strong>Reminder:</strong>
@@ -477,7 +480,8 @@ Custom Description Components receive all [Field Component](#the-field-component
477480

478481
| Property | Description |
479482
| -------------- | ---------------------------------------------------------------- |
480-
| **`field`** | The sanitized, client-friendly version of the field's config. [More details](#the-field-prop) |
483+
| **`field`** | In Server Components, this is the original Field Config. In Client Components, this is the sanitized Client Field Config. [More details](#the-field-prop). |
484+
| **`clientField`** | Server components receive the Client Field Config through this prop. [More details](#the-field-prop). |
481485

482486
<Banner type="success">
483487
<strong>Reminder:</strong>

packages/payload/src/admin/fields/Array.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,36 @@ type ArrayFieldBaseClientProps = {
2727
export type ArrayFieldClientProps = ArrayFieldBaseClientProps &
2828
ClientFieldBase<ArrayFieldClientWithoutType>
2929

30-
export type ArrayFieldServerProps = ServerFieldBase<ArrayField>
30+
export type ArrayFieldServerProps = ServerFieldBase<ArrayField, ArrayFieldClientWithoutType>
3131

32-
export type ArrayFieldServerComponent = FieldServerComponent<ArrayField>
32+
export type ArrayFieldServerComponent = FieldServerComponent<
33+
ArrayField,
34+
ArrayFieldClientWithoutType
35+
>
3336

3437
export type ArrayFieldClientComponent = FieldClientComponent<
3538
ArrayFieldClientWithoutType,
3639
ArrayFieldBaseClientProps
3740
>
3841

39-
export type ArrayFieldLabelServerComponent = FieldLabelServerComponent<ArrayField>
42+
export type ArrayFieldLabelServerComponent = FieldLabelServerComponent<
43+
ArrayField,
44+
ArrayFieldClientWithoutType
45+
>
4046

4147
export type ArrayFieldLabelClientComponent = FieldLabelClientComponent<ArrayFieldClientWithoutType>
4248

43-
export type ArrayFieldDescriptionServerComponent = FieldDescriptionServerComponent<ArrayField>
49+
export type ArrayFieldDescriptionServerComponent = FieldDescriptionServerComponent<
50+
ArrayField,
51+
ArrayFieldClientWithoutType
52+
>
4453

4554
export type ArrayFieldDescriptionClientComponent =
4655
FieldDescriptionClientComponent<ArrayFieldClientWithoutType>
4756

48-
export type ArrayFieldErrorServerComponent = FieldErrorServerComponent<ArrayField>
57+
export type ArrayFieldErrorServerComponent = FieldErrorServerComponent<
58+
ArrayField,
59+
ArrayFieldClientWithoutType
60+
>
4961

5062
export type ArrayFieldErrorClientComponent = FieldErrorClientComponent<ArrayFieldClientWithoutType>

packages/payload/src/admin/fields/Blocks.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,38 @@ type BlocksFieldBaseClientProps = {
2525
export type BlocksFieldClientProps = BlocksFieldBaseClientProps &
2626
ClientFieldBase<BlocksFieldClientWithoutType>
2727

28-
export type BlocksFieldServerProps = ServerFieldBase<BlocksField>
28+
export type BlocksFieldServerProps = ServerFieldBase<BlocksField, BlocksFieldClientWithoutType>
2929

30-
export type BlocksFieldServerComponent = FieldServerComponent<BlocksField>
30+
export type BlocksFieldServerComponent = FieldServerComponent<
31+
BlocksField,
32+
BlocksFieldClientWithoutType
33+
>
3134

3235
export type BlocksFieldClientComponent = FieldClientComponent<
3336
BlocksFieldClientWithoutType,
3437
BlocksFieldBaseClientProps
3538
>
3639

37-
export type BlocksFieldLabelServerComponent = FieldLabelServerComponent<BlocksField>
40+
export type BlocksFieldLabelServerComponent = FieldLabelServerComponent<
41+
BlocksField,
42+
BlocksFieldClientWithoutType
43+
>
3844

3945
export type BlocksFieldLabelClientComponent =
4046
FieldLabelClientComponent<BlocksFieldClientWithoutType>
4147

42-
export type BlocksFieldDescriptionServerComponent = FieldDescriptionServerComponent<BlocksField>
48+
export type BlocksFieldDescriptionServerComponent = FieldDescriptionServerComponent<
49+
BlocksField,
50+
BlocksFieldClientWithoutType
51+
>
4352

4453
export type BlocksFieldDescriptionClientComponent =
4554
FieldDescriptionClientComponent<BlocksFieldClientWithoutType>
4655

47-
export type BlocksFieldErrorServerComponent = FieldErrorServerComponent<BlocksField>
56+
export type BlocksFieldErrorServerComponent = FieldErrorServerComponent<
57+
BlocksField,
58+
BlocksFieldClientWithoutType
59+
>
4860

4961
export type BlocksFieldErrorClientComponent =
5062
FieldErrorClientComponent<BlocksFieldClientWithoutType>

packages/payload/src/admin/fields/Checkbox.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,41 @@ type CheckboxFieldBaseClientProps = {
3030
export type CheckboxFieldClientProps = CheckboxFieldBaseClientProps &
3131
ClientFieldBase<CheckboxFieldClientWithoutType>
3232

33-
export type CheckboxFieldServerProps = ServerFieldBase<CheckboxField>
33+
export type CheckboxFieldServerProps = ServerFieldBase<
34+
CheckboxField,
35+
CheckboxFieldClientWithoutType
36+
>
3437

35-
export type CheckboxFieldServerComponent = FieldServerComponent<CheckboxField>
38+
export type CheckboxFieldServerComponent = FieldServerComponent<
39+
CheckboxField,
40+
CheckboxFieldClientWithoutType
41+
>
3642

3743
export type CheckboxFieldClientComponent = FieldClientComponent<
3844
CheckboxFieldClientWithoutType,
3945
CheckboxFieldBaseClientProps
4046
>
4147

42-
export type CheckboxFieldLabelServerComponent = FieldLabelServerComponent<CheckboxField>
48+
export type CheckboxFieldLabelServerComponent = FieldLabelServerComponent<
49+
CheckboxField,
50+
CheckboxFieldClientWithoutType
51+
>
4352

4453
export type CheckboxFieldLabelClientComponent =
4554
FieldLabelClientComponent<CheckboxFieldClientWithoutType>
4655

47-
export type CheckboxFieldDescriptionServerComponent = FieldDescriptionServerComponent<CheckboxField>
56+
export type CheckboxFieldDescriptionServerComponent = FieldDescriptionServerComponent<
57+
CheckboxField,
58+
CheckboxFieldClientWithoutType
59+
>
4860

4961
export type CheckboxFieldDescriptionClientComponent =
5062
FieldDescriptionClientComponent<CheckboxFieldClientWithoutType>
5163

52-
export type CheckboxFieldErrorServerComponent = FieldErrorServerComponent<CheckboxField>
64+
export type CheckboxFieldErrorServerComponent = FieldErrorServerComponent<
65+
CheckboxField,
66+
CheckboxFieldClientWithoutType
67+
>
5368

5469
export type CheckboxFieldErrorClientComponent =
5570
FieldErrorClientComponent<CheckboxFieldClientWithoutType>

packages/payload/src/admin/fields/Code.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,33 @@ type CodeFieldBaseClientProps = {
2626
export type CodeFieldClientProps = ClientFieldBase<CodeFieldClientWithoutType> &
2727
CodeFieldBaseClientProps
2828

29-
export type CodeFieldServerProps = ServerFieldBase<CodeField>
29+
export type CodeFieldServerProps = ServerFieldBase<CodeField, CodeFieldClientWithoutType>
3030

31-
export type CodeFieldServerComponent = FieldServerComponent<CodeField>
31+
export type CodeFieldServerComponent = FieldServerComponent<CodeField, CodeFieldClientWithoutType>
3232

3333
export type CodeFieldClientComponent = FieldClientComponent<
3434
CodeFieldClientWithoutType,
3535
CodeFieldBaseClientProps
3636
>
3737

38-
export type CodeFieldLabelServerComponent = FieldLabelServerComponent<CodeField>
38+
export type CodeFieldLabelServerComponent = FieldLabelServerComponent<
39+
CodeField,
40+
CodeFieldClientWithoutType
41+
>
3942

4043
export type CodeFieldLabelClientComponent = FieldLabelClientComponent<CodeFieldClientWithoutType>
4144

42-
export type CodeFieldDescriptionServerComponent = FieldDescriptionServerComponent<CodeField>
45+
export type CodeFieldDescriptionServerComponent = FieldDescriptionServerComponent<
46+
CodeField,
47+
CodeFieldClientWithoutType
48+
>
4349

4450
export type CodeFieldDescriptionClientComponent =
4551
FieldDescriptionClientComponent<CodeFieldClientWithoutType>
4652

47-
export type CodeFieldErrorServerComponent = FieldErrorServerComponent<CodeField>
53+
export type CodeFieldErrorServerComponent = FieldErrorServerComponent<
54+
CodeField,
55+
CodeFieldClientWithoutType
56+
>
4857

4958
export type CodeFieldErrorClientComponent = FieldErrorClientComponent<CodeFieldClientWithoutType>

packages/payload/src/admin/fields/Collapsible.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,39 @@ type CollapsibleFieldClientWithoutType = MarkOptional<CollapsibleFieldClient, 't
1919

2020
export type CollapsibleFieldClientProps = ClientFieldBase<CollapsibleFieldClientWithoutType>
2121

22-
export type CollapsibleFieldServerProps = ServerFieldBase<CollapsibleField>
22+
export type CollapsibleFieldServerProps = ServerFieldBase<
23+
CollapsibleField,
24+
CollapsibleFieldClientWithoutType
25+
>
2326

24-
export type CollapsibleFieldServerComponent = FieldServerComponent<CollapsibleField>
27+
export type CollapsibleFieldServerComponent = FieldServerComponent<
28+
CollapsibleField,
29+
CollapsibleFieldClientWithoutType
30+
>
2531

2632
export type CollapsibleFieldClientComponent =
2733
FieldClientComponent<CollapsibleFieldClientWithoutType>
2834

29-
export type CollapsibleFieldLabelServerComponent = FieldLabelServerComponent<CollapsibleField>
35+
export type CollapsibleFieldLabelServerComponent = FieldLabelServerComponent<
36+
CollapsibleField,
37+
CollapsibleFieldClientWithoutType
38+
>
3039

3140
export type CollapsibleFieldLabelClientComponent =
3241
FieldLabelClientComponent<CollapsibleFieldClientWithoutType>
3342

34-
export type CollapsibleFieldDescriptionServerComponent =
35-
FieldDescriptionServerComponent<CollapsibleField>
43+
export type CollapsibleFieldDescriptionServerComponent = FieldDescriptionServerComponent<
44+
CollapsibleField,
45+
CollapsibleFieldClientWithoutType
46+
>
3647

3748
export type CollapsibleFieldDescriptionClientComponent =
3849
FieldDescriptionClientComponent<CollapsibleFieldClientWithoutType>
3950

40-
export type CollapsibleFieldErrorServerComponent = FieldErrorServerComponent<CollapsibleField>
51+
export type CollapsibleFieldErrorServerComponent = FieldErrorServerComponent<
52+
CollapsibleField,
53+
CollapsibleFieldClientWithoutType
54+
>
4155

4256
export type CollapsibleFieldErrorClientComponent =
4357
FieldErrorClientComponent<CollapsibleFieldClientWithoutType>

packages/payload/src/admin/fields/Date.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,33 @@ type DateFieldBaseClientProps = {
2525
export type DateFieldClientProps = ClientFieldBase<DateFieldClientWithoutType> &
2626
DateFieldBaseClientProps
2727

28-
export type DateFieldServerProps = ServerFieldBase<DateField>
28+
export type DateFieldServerProps = ServerFieldBase<DateField, DateFieldClientWithoutType>
2929

30-
export type DateFieldServerComponent = FieldServerComponent<DateField>
30+
export type DateFieldServerComponent = FieldServerComponent<DateField, DateFieldClientWithoutType>
3131

3232
export type DateFieldClientComponent = FieldClientComponent<
3333
DateFieldClientWithoutType,
3434
DateFieldBaseClientProps
3535
>
3636

37-
export type DateFieldLabelServerComponent = FieldLabelServerComponent<DateField>
37+
export type DateFieldLabelServerComponent = FieldLabelServerComponent<
38+
DateField,
39+
DateFieldClientWithoutType
40+
>
3841

3942
export type DateFieldLabelClientComponent = FieldLabelClientComponent<DateFieldClientWithoutType>
4043

41-
export type DateFieldDescriptionServerComponent = FieldDescriptionServerComponent<DateField>
44+
export type DateFieldDescriptionServerComponent = FieldDescriptionServerComponent<
45+
DateField,
46+
DateFieldClientWithoutType
47+
>
4248

4349
export type DateFieldDescriptionClientComponent =
4450
FieldDescriptionClientComponent<DateFieldClientWithoutType>
4551

46-
export type DateFieldErrorServerComponent = FieldErrorServerComponent<DateField>
52+
export type DateFieldErrorServerComponent = FieldErrorServerComponent<
53+
DateField,
54+
DateFieldClientWithoutType
55+
>
4756

4857
export type DateFieldErrorClientComponent = FieldErrorClientComponent<DateFieldClientWithoutType>

0 commit comments

Comments
 (0)