Skip to content

Commit

Permalink
fix(ui): automatically subscribes custom fields to conditional logic (#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
jacobsfletch authored Dec 13, 2024
1 parent 33d5482 commit 1502e09
Show file tree
Hide file tree
Showing 32 changed files with 186 additions and 49 deletions.
6 changes: 3 additions & 3 deletions packages/payload/src/queues/localAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ export const getJobsLocalAPI = (payload: Payload) => ({
req?: PayloadRequest
// TTaskOrWorkflowlug with keyof TypedJobs['workflows'] removed:
task: TTaskOrWorkflowSlug extends keyof TypedJobs['tasks'] ? TTaskOrWorkflowSlug : never
workflow?: never
waitUntil?: Date
workflow?: never
}
| {
input: TypedJobs['workflows'][TTaskOrWorkflowSlug]['input']
queue?: string
req?: PayloadRequest
task?: never
waitUntil?: Date
workflow: TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
? TTaskOrWorkflowSlug
: never
waitUntil?: Date
},
): Promise<
TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
Expand All @@ -60,8 +60,8 @@ export const getJobsLocalAPI = (payload: Payload) => ({
input: args.input,
queue,
taskSlug: 'task' in args ? args.task : undefined,
workflowSlug: 'workflow' in args ? args.workflow : undefined,
waitUntil: args.waitUntil?.toISOString() ?? undefined,
workflowSlug: 'workflow' in args ? args.workflow : undefined,
} as BaseJob,
req: args.req,
})) as TTaskOrWorkflowSlug extends keyof TypedJobs['workflows']
Expand Down
3 changes: 1 addition & 2 deletions packages/richtext-lexical/src/field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
RenderCustomComponent,
useEditDepth,
useField,
withCondition,
} from '@payloadcms/ui'
import { mergeFieldStyles } from '@payloadcms/ui/shared'
import React, { useCallback, useMemo } from 'react'
Expand Down Expand Up @@ -143,4 +142,4 @@ function fallbackRender({ error }: { error: Error }) {
)
}

export const RichText: typeof RichTextComponent = withCondition(RichTextComponent)
export const RichText: typeof RichTextComponent = RichTextComponent
3 changes: 1 addition & 2 deletions packages/richtext-slate/src/field/RichText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
useEditDepth,
useField,
useTranslation,
withCondition,
} from '@payloadcms/ui'
import { mergeFieldStyles } from '@payloadcms/ui/shared'
import { isHotkey } from 'is-hotkey'
Expand Down Expand Up @@ -459,4 +458,4 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
)
}

export const RichText = withCondition(RichTextField)
export const RichText = RichTextField
1 change: 1 addition & 0 deletions packages/ui/src/exports/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export { useField } from '../../forms/useField/index.js'
export type { FieldType, Options } from '../../forms/useField/types.js'

export { withCondition } from '../../forms/withCondition/index.js'
export { WatchCondition } from '../../forms/withCondition/WatchCondition.js'

// graphics
export { Account } from '../../graphics/Account/index.js'
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/forms/RenderFields/RenderField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ export function RenderField({
readOnly,
schemaPath,
}: RenderFieldProps) {
const Field = useFormFields(([fields]) => fields && fields?.[path]?.customComponents?.Field)
const CustomField = useFormFields(([fields]) => fields && fields?.[path]?.customComponents?.Field)

if (Field !== undefined) {
return Field || null
if (CustomField !== undefined) {
return CustomField || null
}

const baseFieldProps: Pick<ClientComponentProps, 'forceRender' | 'readOnly' | 'schemaPath'> = {
Expand Down
36 changes: 22 additions & 14 deletions packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { RenderFieldMethod } from './types.js'
import { RenderServerComponent } from '../../elements/RenderServerComponent/index.js'

// 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
import { FieldDescription } from '../../exports/client/index.js'
import { FieldDescription, WatchCondition } from '../../exports/client/index.js'

const defaultUIFieldComponentKeys: Array<'Cell' | 'Description' | 'Field' | 'Filter'> = [
'Cell',
Expand Down Expand Up @@ -135,12 +135,16 @@ export const renderField: RenderFieldMethod = ({
fieldConfig.admin.components = {}
}

fieldState.customComponents.Field = RenderServerComponent({
clientProps,
Component: fieldConfig.editor.FieldComponent,
importMap: req.payload.importMap,
serverProps,
})
fieldState.customComponents.Field = (
<WatchCondition path={path}>
{RenderServerComponent({
clientProps,
Component: fieldConfig.editor.FieldComponent,
importMap: req.payload.importMap,
serverProps,
})}
</WatchCondition>
)

break
}
Expand Down Expand Up @@ -236,13 +240,17 @@ export const renderField: RenderFieldMethod = ({
}

if ('Field' in fieldConfig.admin.components) {
fieldState.customComponents.Field = RenderServerComponent({
clientProps,
Component: fieldConfig.admin.components.Field,
importMap: req.payload.importMap,
key: 'field.admin.components.Field',
serverProps,
})
fieldState.customComponents.Field = (
<WatchCondition path={path}>
{RenderServerComponent({
clientProps,
Component: fieldConfig.admin.components.Field,
importMap: req.payload.importMap,
key: 'field.admin.components.Field',
serverProps,
})}
</WatchCondition>
)
}
}
}
Expand Down
1 change: 0 additions & 1 deletion packages/ui/src/forms/withCondition/WatchCondition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { useFormFields } from '../Form/context.js'

export const WatchCondition: React.FC<{
children: React.ReactNode
indexPath: string
path: string
}> = (props) => {
const { children, path } = props
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/forms/withCondition/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export const withCondition = <P extends MarkOptional<FieldPaths, 'indexPath' | '
Field: React.ComponentType<P>,
): React.FC<P> => {
const CheckForCondition: React.FC<P> = (props) => {
const { indexPath, path } = props
const { path } = props

return (
<WatchCondition indexPath={indexPath} path={path}>
<WatchCondition path={path}>
<Field {...props} />
</WatchCondition>
)
Expand Down
2 changes: 1 addition & 1 deletion templates/_template/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}
}
2 changes: 1 addition & 1 deletion templates/_template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
"sharp": "0.32.6"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@types/node": "^22.5.4",
"@types/react": "19.0.1",
"@types/react-dom": "19.0.1",
"eslint": "^9.16.0",
"eslint-config-next": "15.1.0",
"@eslint/eslintrc": "^3.2.0",
"prettier": "^3.4.2",
"typescript": "5.7.2"
},
Expand Down
2 changes: 1 addition & 1 deletion templates/blank/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}
}
2 changes: 1 addition & 1 deletion templates/blank/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
"sharp": "0.32.6"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@types/node": "^22.5.4",
"@types/react": "19.0.1",
"@types/react-dom": "19.0.1",
"eslint": "^9.16.0",
"eslint-config-next": "15.1.0",
"@eslint/eslintrc": "^3.2.0",
"prettier": "^3.4.2",
"typescript": "5.7.2"
},
Expand Down
2 changes: 1 addition & 1 deletion templates/website/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}
}
4 changes: 2 additions & 2 deletions templates/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@tailwindcss/typography": "^0.5.13",
"@types/escape-html": "^1.0.2",
"@types/jsonwebtoken": "^9.0.6",
Expand All @@ -64,9 +65,8 @@
"copyfiles": "^2.4.1",
"eslint": "^9.16.0",
"eslint-config-next": "15.1.0",
"@eslint/eslintrc": "^3.2.0",
"prettier": "^3.4.2",
"postcss": "^8.4.38",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.3",
"typescript": "5.7.2"
},
Expand Down
2 changes: 1 addition & 1 deletion templates/with-payload-cloud/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}
}
2 changes: 1 addition & 1 deletion templates/with-payload-cloud/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
"sharp": "0.32.6"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@types/node": "^22.5.4",
"@types/react": "19.0.1",
"@types/react-dom": "19.0.1",
"eslint": "^9.16.0",
"eslint-config-next": "15.1.0",
"@eslint/eslintrc": "^3.2.0",
"prettier": "^3.4.2",
"typescript": "5.7.2"
},
Expand Down
2 changes: 1 addition & 1 deletion templates/with-postgres/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}
}
2 changes: 1 addition & 1 deletion templates/with-postgres/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
"sharp": "0.32.6"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@types/node": "^22.5.4",
"@types/react": "19.0.1",
"@types/react-dom": "19.0.1",
"eslint": "^9.16.0",
"eslint-config-next": "15.1.0",
"@eslint/eslintrc": "^3.2.0",
"prettier": "^3.4.2",
"typescript": "5.7.2"
},
Expand Down
2 changes: 1 addition & 1 deletion templates/with-vercel-mongodb/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}
}
2 changes: 1 addition & 1 deletion templates/with-vercel-mongodb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
"react-dom": "19.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@types/node": "^22.5.4",
"@types/react": "19.0.1",
"@types/react-dom": "19.0.1",
"eslint": "^9.16.0",
"eslint-config-next": "15.1.0",
"@eslint/eslintrc": "^3.2.0",
"prettier": "^3.4.2",
"typescript": "5.7.2"
},
Expand Down
2 changes: 1 addition & 1 deletion templates/with-vercel-postgres/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}
}
2 changes: 1 addition & 1 deletion templates/with-vercel-postgres/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
"react-dom": "19.0.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@types/node": "^22.5.4",
"@types/react": "19.0.1",
"@types/react-dom": "19.0.1",
"eslint": "^9.16.0",
"eslint-config-next": "15.1.0",
"@eslint/eslintrc": "^3.2.0",
"prettier": "^3.4.2",
"typescript": "5.7.2"
},
Expand Down
2 changes: 1 addition & 1 deletion templates/with-vercel-website/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
}
}
4 changes: 2 additions & 2 deletions templates/with-vercel-website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@tailwindcss/typography": "^0.5.13",
"@types/escape-html": "^1.0.2",
"@types/jsonwebtoken": "^9.0.6",
Expand All @@ -66,9 +67,8 @@
"copyfiles": "^2.4.1",
"eslint": "^9.16.0",
"eslint-config-next": "^15.1.0",
"@eslint/eslintrc": "^3.2.0",
"prettier": "^3.4.2",
"postcss": "^8.4.38",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.3",
"typescript": "5.7.2"
},
Expand Down
11 changes: 11 additions & 0 deletions test/fields/collections/ConditionalLogic/CustomClientField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client'

import type { TextFieldClientComponent } from 'payload'

import React from 'react'

const CustomClientField: TextFieldClientComponent = () => {
return <div id="custom-client-field">Custom Client Field</div>
}

export default CustomClientField
11 changes: 11 additions & 0 deletions test/fields/collections/ConditionalLogic/CustomFieldWithField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client'
import type { TextFieldClientComponent } from 'payload'

import { TextField } from '@payloadcms/ui'
import React from 'react'

const CustomFieldWithField: TextFieldClientComponent = (props) => {
return <TextField {...props} />
}

export default CustomFieldWithField
11 changes: 11 additions & 0 deletions test/fields/collections/ConditionalLogic/CustomFieldWithHOC.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client'
import type { TextFieldClientComponent } from 'payload'

import { TextField, withCondition } from '@payloadcms/ui'
import React from 'react'

const MyField: TextFieldClientComponent = (props) => {
return <TextField {...props} />
}

export default withCondition(MyField)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { TextFieldServerComponent } from 'payload'

import React from 'react'

const CustomServerField: TextFieldServerComponent = () => {
return <div id="custom-server-field">Custom Server Field</div>
}

export default CustomServerField
Loading

0 comments on commit 1502e09

Please sign in to comment.