Skip to content

Commit 1502e09

Browse files
authored
fix(ui): automatically subscribes custom fields to conditional logic (#9928)
Currently, custom components do not respect `admin.condition` unless manually wrapped with the `withCondition` HOC, like all default fields currently do. This should not be a requirement of component authors. Instead, we can automatically detect custom client and server fields and wrap them with the underlying `WatchCondition` component which will subscribe to the `passesCondition` property within client-side form state. For my future self: there are potentially multiple instances where fields subscribe to conditions duplicately, such as when rendering a default Payload field within a custom field component. This was always a problem and it is non-breaking, but needs to be reevaluated and removed in the future for performance. Only the default fields that Payload renders client-side need to subscribe to field conditions in this way. When importing a Payload field into your custom field component, for example, it should not include the HOC, because custom components now watch conditions themselves.
1 parent 33d5482 commit 1502e09

File tree

32 files changed

+186
-49
lines changed

32 files changed

+186
-49
lines changed

packages/payload/src/queues/localAPI.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,18 @@ export const getJobsLocalAPI = (payload: Payload) => ({
2222
req?: PayloadRequest
2323
// TTaskOrWorkflowlug with keyof TypedJobs['workflows'] removed:
2424
task: TTaskOrWorkflowSlug extends keyof TypedJobs['tasks'] ? TTaskOrWorkflowSlug : never
25-
workflow?: never
2625
waitUntil?: Date
26+
workflow?: never
2727
}
2828
| {
2929
input: TypedJobs['workflows'][TTaskOrWorkflowSlug]['input']
3030
queue?: string
3131
req?: PayloadRequest
3232
task?: never
33+
waitUntil?: Date
3334
workflow: TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
3435
? TTaskOrWorkflowSlug
3536
: never
36-
waitUntil?: Date
3737
},
3838
): Promise<
3939
TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
@@ -60,8 +60,8 @@ export const getJobsLocalAPI = (payload: Payload) => ({
6060
input: args.input,
6161
queue,
6262
taskSlug: 'task' in args ? args.task : undefined,
63-
workflowSlug: 'workflow' in args ? args.workflow : undefined,
6463
waitUntil: args.waitUntil?.toISOString() ?? undefined,
64+
workflowSlug: 'workflow' in args ? args.workflow : undefined,
6565
} as BaseJob,
6666
req: args.req,
6767
})) as TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']

packages/richtext-lexical/src/field/Field.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
RenderCustomComponent,
1010
useEditDepth,
1111
useField,
12-
withCondition,
1312
} from '@payloadcms/ui'
1413
import { mergeFieldStyles } from '@payloadcms/ui/shared'
1514
import React, { useCallback, useMemo } from 'react'
@@ -143,4 +142,4 @@ function fallbackRender({ error }: { error: Error }) {
143142
)
144143
}
145144

146-
export const RichText: typeof RichTextComponent = withCondition(RichTextComponent)
145+
export const RichText: typeof RichTextComponent = RichTextComponent

packages/richtext-slate/src/field/RichText.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
useEditDepth,
1515
useField,
1616
useTranslation,
17-
withCondition,
1817
} from '@payloadcms/ui'
1918
import { mergeFieldStyles } from '@payloadcms/ui/shared'
2019
import { isHotkey } from 'is-hotkey'
@@ -459,4 +458,4 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
459458
)
460459
}
461460

462-
export const RichText = withCondition(RichTextField)
461+
export const RichText = RichTextField

packages/ui/src/exports/client/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export { useField } from '../../forms/useField/index.js'
196196
export type { FieldType, Options } from '../../forms/useField/types.js'
197197

198198
export { withCondition } from '../../forms/withCondition/index.js'
199+
export { WatchCondition } from '../../forms/withCondition/WatchCondition.js'
199200

200201
// graphics
201202
export { Account } from '../../graphics/Account/index.js'

packages/ui/src/forms/RenderFields/RenderField.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ export function RenderField({
5151
readOnly,
5252
schemaPath,
5353
}: RenderFieldProps) {
54-
const Field = useFormFields(([fields]) => fields && fields?.[path]?.customComponents?.Field)
54+
const CustomField = useFormFields(([fields]) => fields && fields?.[path]?.customComponents?.Field)
5555

56-
if (Field !== undefined) {
57-
return Field || null
56+
if (CustomField !== undefined) {
57+
return CustomField || null
5858
}
5959

6060
const baseFieldProps: Pick<ClientComponentProps, 'forceRender' | 'readOnly' | 'schemaPath'> = {

packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { RenderFieldMethod } from './types.js'
99
import { RenderServerComponent } from '../../elements/RenderServerComponent/index.js'
1010

1111
// eslint-disable-next-line payload/no-imports-from-exports-dir -- need this to reference already existing bundle. Otherwise, bundle size increases., payload/no-imports-from-exports-dir
12-
import { FieldDescription } from '../../exports/client/index.js'
12+
import { FieldDescription, WatchCondition } from '../../exports/client/index.js'
1313

1414
const defaultUIFieldComponentKeys: Array<'Cell' | 'Description' | 'Field' | 'Filter'> = [
1515
'Cell',
@@ -135,12 +135,16 @@ export const renderField: RenderFieldMethod = ({
135135
fieldConfig.admin.components = {}
136136
}
137137

138-
fieldState.customComponents.Field = RenderServerComponent({
139-
clientProps,
140-
Component: fieldConfig.editor.FieldComponent,
141-
importMap: req.payload.importMap,
142-
serverProps,
143-
})
138+
fieldState.customComponents.Field = (
139+
<WatchCondition path={path}>
140+
{RenderServerComponent({
141+
clientProps,
142+
Component: fieldConfig.editor.FieldComponent,
143+
importMap: req.payload.importMap,
144+
serverProps,
145+
})}
146+
</WatchCondition>
147+
)
144148

145149
break
146150
}
@@ -236,13 +240,17 @@ export const renderField: RenderFieldMethod = ({
236240
}
237241

238242
if ('Field' in fieldConfig.admin.components) {
239-
fieldState.customComponents.Field = RenderServerComponent({
240-
clientProps,
241-
Component: fieldConfig.admin.components.Field,
242-
importMap: req.payload.importMap,
243-
key: 'field.admin.components.Field',
244-
serverProps,
245-
})
243+
fieldState.customComponents.Field = (
244+
<WatchCondition path={path}>
245+
{RenderServerComponent({
246+
clientProps,
247+
Component: fieldConfig.admin.components.Field,
248+
importMap: req.payload.importMap,
249+
key: 'field.admin.components.Field',
250+
serverProps,
251+
})}
252+
</WatchCondition>
253+
)
246254
}
247255
}
248256
}

packages/ui/src/forms/withCondition/WatchCondition.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { useFormFields } from '../Form/context.js'
66

77
export const WatchCondition: React.FC<{
88
children: React.ReactNode
9-
indexPath: string
109
path: string
1110
}> = (props) => {
1211
const { children, path } = props

packages/ui/src/forms/withCondition/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ export const withCondition = <P extends MarkOptional<FieldPaths, 'indexPath' | '
1010
Field: React.ComponentType<P>,
1111
): React.FC<P> => {
1212
const CheckForCondition: React.FC<P> = (props) => {
13-
const { indexPath, path } = props
13+
const { path } = props
1414

1515
return (
16-
<WatchCondition indexPath={indexPath} path={path}>
16+
<WatchCondition path={path}>
1717
<Field {...props} />
1818
</WatchCondition>
1919
)

templates/_template/.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@
3636
"editor.codeActionsOnSave": {
3737
"source.fixAll.eslint": "explicit"
3838
}
39-
},
39+
}
4040
}

templates/_template/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
"sharp": "0.32.6"
2929
},
3030
"devDependencies": {
31+
"@eslint/eslintrc": "^3.2.0",
3132
"@types/node": "^22.5.4",
3233
"@types/react": "19.0.1",
3334
"@types/react-dom": "19.0.1",
3435
"eslint": "^9.16.0",
3536
"eslint-config-next": "15.1.0",
36-
"@eslint/eslintrc": "^3.2.0",
3737
"prettier": "^3.4.2",
3838
"typescript": "5.7.2"
3939
},

0 commit comments

Comments
 (0)