From a62cdc89d887780b33568fde9ca45c047ffd489f Mon Sep 17 00:00:00 2001
From: Philipp Schneider <47689073+philipp-tailor@users.noreply.github.com>
Date: Fri, 2 May 2025 16:18:11 +0200
Subject: [PATCH] fix(ui): blockType ignored when merging server form state
(#12207)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
In this case, the `blockType` property is created on the server, but -
prior to this fix - was discarded on the client in
[`fieldReducer.ts`](https://github.com/payloadcms/payload/blob/main/packages/ui/src/forms/Form/fieldReducer.ts#L186-L198)
via
[`mergerServerFormState.ts`](https://github.com/payloadcms/payload/blob/b9832f40e41fff0a5a5b7bf7b08032aa2d6e8b4e/packages/ui/src/forms/Form/mergeServerFormState.ts#L29-L31),
because the field's path neither existed in the client's form state, nor
was it marked as `addedByServer`.
This caused later calls to POST requests to form state to send without
the `blockType` key for block rows, which in turn caused
`addFieldStatePromise.ts` to throw the following error:
```
Block with type "undefined" was found in block data, but no block with that type is defined in the config for field with schema path ${schemaPath}.
```
This prevented the client side form state update from completing, and if
the form state was saved, broke the document.
This is a follow-up to #12131, which treated the symptom, but not the
cause. The original issue seems to have been introduced in
https://github.com/payloadcms/payload/releases/tag/v3.34.0. It's unclear
to me whether this issue is connected to block E2E tests having been
disabled in the same release in
https://github.com/payloadcms/payload/pull/11988.
## How to reproduce
### Collection configuration
```ts
const RICH_TEXT_BLOCK_TYPE = 'richTextBlockType'
const RichTextBlock: Block = {
slug: RICH_TEXT_BLOCK_TYPE,
interfaceName: 'RichTextBlock',
fields: [
{
name: 'richTextBlockField',
label: 'Rich Text Field in Block Field',
type: 'richText',
editor: lexicalEditor({}),
required: true,
},
],
}
const MyCollection: CollectionConfig = {
slug: 'my-collection-slug,
fields: [
{
name: 'arrayField',
label: 'Array Field',
type: 'array',
fields: [
{
name: 'blockField',
type: 'blocks',
blocks: [RichTextBlock],
required: true,
},
],
},
]
}
export default MyCollection
```
### Steps
- Press "Add Array Field"
--> ✅ 1st block with rich text is added
- Press "Add Array Field" a 2nd time
### Result
- 🛑 2nd block is indefinitely in loading state (side-note: the form UI
should preferably explicitly indicate the error).
- 🛑 If saving the document, it is corrupted and will only show a blank
page (also not indicating any error).
Client side:
API error:
Client side, when saving and re-opening document (API error of `GET
/admin/collections/${myCollection}/${documentId}` is the same (arguably
the HTTP response status code shouldn't be `200`)):
### Result after fix
- `blockType` is sent from the client to the server.
- ✅ 2nd block with rich text is added.
- ✅ Document does not break when saving & re-opening.
---------
Co-authored-by: Jacob Fletcher
---
.../forms/fieldSchemasToFormState/addFieldStatePromise.ts | 4 ++++
test/form-state/int.spec.ts | 7 +++++++
2 files changed, 11 insertions(+)
diff --git a/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts b/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts
index b3896560dc..56035e4d1e 100644
--- a/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts
+++ b/packages/ui/src/forms/fieldSchemasToFormState/addFieldStatePromise.ts
@@ -456,6 +456,10 @@ export const addFieldStatePromise = async (args: AddFieldStatePromiseArgs): Prom
value: row.blockType,
}
+ if (addedByServer) {
+ state[fieldKey].addedByServer = addedByServer
+ }
+
if (includeSchema) {
state[fieldKey].fieldSchema = block.fields.find(
(blockField) => 'name' in blockField && blockField.name === 'blockType',
diff --git a/test/form-state/int.spec.ts b/test/form-state/int.spec.ts
index 525e99e419..d81d4ae73f 100644
--- a/test/form-state/int.spec.ts
+++ b/test/form-state/int.spec.ts
@@ -228,6 +228,12 @@ describe('Form State', () => {
collection: postsSlug,
data: {
title: 'Test Post',
+ blocks: [
+ {
+ blockType: 'text',
+ text: 'Test block',
+ },
+ ],
},
})
@@ -248,6 +254,7 @@ describe('Form State', () => {
})
expect(state.title?.addedByServer).toBe(true)
+ expect(state['blocks.0.blockType']?.addedByServer).toBe(true)
// Ensure that `addedByServer` is removed after being received by the client
const newState = mergeServerFormState({