fix(ui): addFieldRow set modified (#9324)

Fixes #9264. When externally updating array or block rows through the
`addFieldRow` or `replaceFieldRow` methods, nested rich text fields
along with any custom components within them are never rendered. This is
because unless the form is explicitly set to modified, as the default
array and blocks fields currently do, the newly generated form-state
will skip the rendering step. Now, the underlying callbacks themselves
automatically set the form to modified to trigger rendering.
This commit is contained in:
Jacob Fletcher
2024-11-19 08:52:50 -05:00
committed by GitHub
parent a50029f659
commit 0f3f6e73da
9 changed files with 93 additions and 10 deletions

View File

@@ -52,6 +52,7 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
schemaPath: schemaPathFromProps, schemaPath: schemaPathFromProps,
validate, validate,
} = props } = props
const schemaPath = schemaPathFromProps ?? name const schemaPath = schemaPathFromProps ?? name
const minRows = (minRowsProp ?? required) ? 1 : 0 const minRows = (minRowsProp ?? required) ? 1 : 0
@@ -129,13 +130,11 @@ export const ArrayFieldComponent: ArrayFieldClientComponent = (props) => {
schemaPath, schemaPath,
}) })
setModified(true)
setTimeout(() => { setTimeout(() => {
scrollToID(`${path}-row-${rowIndex}`) scrollToID(`${path}-row-${rowIndex}`)
}, 0) }, 0)
}, },
[addFieldRow, path, schemaPath, setModified], [addFieldRow, path, schemaPath],
) )
const duplicateRow = useCallback( const duplicateRow = useCallback(

View File

@@ -116,13 +116,11 @@ const BlocksFieldComponent: BlocksFieldClientComponent = (props) => {
schemaPath, schemaPath,
}) })
setModified(true)
setTimeout(() => { setTimeout(() => {
scrollToID(`${path}-row-${rowIndex + 1}`) scrollToID(`${path}-row-${rowIndex + 1}`)
}, 0) }, 0)
}, },
[addFieldRow, path, schemaPath, setModified], [addFieldRow, path, schemaPath],
) )
const duplicateRow = useCallback( const duplicateRow = useCallback(

View File

@@ -518,6 +518,8 @@ export const Form: React.FC<FormProps> = (props) => {
rowIndex, rowIndex,
subFieldState, subFieldState,
}) })
setModified(true)
}, },
[dispatchFields, getDataByPath], [dispatchFields, getDataByPath],
) )
@@ -541,6 +543,8 @@ export const Form: React.FC<FormProps> = (props) => {
rowIndex, rowIndex,
subFieldState, subFieldState,
}) })
setModified(true)
}, },
[dispatchFields, getDataByPath], [dispatchFields, getDataByPath],
) )

View File

@@ -40,9 +40,9 @@ export interface Config {
user: User & { user: User & {
collection: 'users'; collection: 'users';
}; };
jobs?: { jobs: {
tasks: unknown; tasks: unknown;
workflows?: unknown; workflows: unknown;
}; };
} }
export interface UserAuthOperations { export interface UserAuthOperations {

View File

@@ -0,0 +1,29 @@
'use client'
import { useForm } from '@payloadcms/ui'
const AddRowButton = () => {
const { addFieldRow } = useForm()
const handleClick = () => {
addFieldRow({
path: 'externallyUpdatedArray',
schemaPath: 'externallyUpdatedArray',
subFieldState: {
text: {
initialValue: 'Hello, world!',
valid: true,
value: 'Hello, world!',
},
},
})
}
return (
<button id="updateArrayExternally" onClick={handleClick} type="button">
Add Row
</button>
)
}
export default AddRowButton

View File

@@ -0,0 +1,11 @@
import type { TextFieldServerComponent } from 'payload'
import { TextField } from '@payloadcms/ui'
export const CustomField: TextFieldServerComponent = ({ clientField, path }) => {
return (
<div id="custom-field">
<TextField field={clientField} path={path as string} />
</div>
)
}

View File

@@ -295,4 +295,10 @@ describe('Array', () => {
'Updated 3 Array Fields successfully.', 'Updated 3 Array Fields successfully.',
) )
}) })
test('should externally update array rows and render custom fields', async () => {
await page.goto(url.create)
await page.locator('#updateArrayExternally').click()
await expect(page.locator('#custom-field')).toBeVisible()
})
}) })

View File

@@ -183,6 +183,30 @@ const ArrayFields: CollectionConfig = {
}, },
], ],
}, },
{
name: 'externallyUpdatedArray',
type: 'array',
fields: [
{
name: 'customField',
type: 'ui',
admin: {
components: {
Field: '/collections/Array/CustomField.js#CustomField',
},
},
},
],
},
{
name: 'ui',
type: 'ui',
admin: {
components: {
Field: '/collections/Array/AddRowButton.js',
},
},
},
], ],
slug: arrayFieldsSlug, slug: arrayFieldsSlug,
versions: true, versions: true,

View File

@@ -465,6 +465,11 @@ export interface ArrayField {
id?: string | null; id?: string | null;
}[] }[]
| null; | null;
externallyUpdatedArray?:
| {
id?: string | null;
}[]
| null;
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
} }
@@ -2084,6 +2089,13 @@ export interface ArrayFieldsSelect<T extends boolean = true> {
}; };
id?: T; id?: T;
}; };
externallyUpdatedArray?:
| T
| {
customField?: T;
id?: T;
};
ui?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }
@@ -3400,6 +3412,6 @@ export interface Auth {
declare module 'payload' { declare module 'payload' {
// @ts-ignore // @ts-ignore
export interface GeneratedTypes extends Config {} export interface GeneratedTypes extends Config {}
} }