Describe the Bug
When a Lexical block contains an array field whose items include a richText sub-field, saving the document causes the outer richText editor to remount and revert all content inside the block to its state at page load. A hard refresh restores the correct saved content.
This issue is particularly annoying when paired with auto-save since it will revert states causing editors to lose work.
Disclaimer: I also added a "Root Cause" and a "Workaround" section but these were provided by AI so take it with a grain of salt. I tested the workaround and it does indeed work.
Link to the code that reproduces this issue
https://github.com/lorand-kis/payload-issue-lexical-block-with-array-field
Reproduction Steps
Steps to reproduce
- Define a Lexical block with an array where each item contains a richText field alongside another array (or use the reproduction repo):
export const MyBlock: Block = {
slug: 'my-block',
fields: [
{
name: 'items',
type: 'array',
fields: [
{
name: 'body',
type: 'richText',
editor: lexicalEditor({
features: ({ defaultFeatures }) => [...defaultFeatures],
}),
},
{
name: 'subitems',
type: 'array',
fields: [{ name: 'label', type: 'text' }],
},
],
},
],
}
- Open a document in the admin panel containing this block, with at least one row in items and subitems left empty for each row
- Edit the body richText field inside one of the rows
- Save the document
- The edited content reverts to what it was when the page first loaded
Expected behavior
The editor retains the saved content without remounting.
Actual behavior
The outer richText editor remounts after every save and all content inside affected blocks reverts to the page-load state.
Root cause
Three bugs interact to produce this:
- Structural mismatch between client and server Lexical state
On the client, removeEmptyArrayValues marks empty array fields with disableFormData: true. When the Lexical editor serializes the block node to JSON, those fields are omitted — so the saved state contains { body: "..." } with no subitems key. On the server, afterRead/promise.js:351 unconditionally normalizes absent array fields to []. The PATCH and getFormState responses therefore contain { body: "...", subitems: [] }.
- The structural mismatch triggers an unnecessary editor remount on every save
After every save, MERGE_SERVER_STATE updates the outer richText field with the server's Lexical state. handleInitialValueChange in Field.js compares the client state (no subitems key) against the server state (subitems: []) using dequal. The structural difference makes dequal return false, causing setRerenderProviderKey(new Date()) to fire and remount the entire outer Lexical editor.
- The remounted block initializer falls back to stale page-load data
On remount, the block component's useState lazy initializer runs. It looks up current field values in formData using a fieldName in formData check. Because nested fields are stored under dot-notation keys (e.g. items.0.body), this top-level check always fails. The initializer falls back to initialLexicalFormState — the form state captured at page load — and all block fields render with their original values.
Workaround
Add a collection-level afterRead hook that strips the empty array keys from the affected block nodes in the Lexical JSON after Payload's normalization runs, so the server response matches the client state and dequal passes.
Which area(s) are affected?
plugin: richtext-lexical
Environment Info
Binaries:
Node: 22.22.0
npm: 10.9.4
Yarn: N/A
pnpm: 10.28.2
Relevant Packages:
payload: 3.82.1
Operating System:
Platform: linux
Arch: x64
Version: #1 SMP PREEMPT_DYNAMIC Thu Jun 5 18:30:46 UTC 2025
Available memory (MB): 31809
Available CPU cores: 24
Describe the Bug
When a Lexical block contains an array field whose items include a richText sub-field, saving the document causes the outer richText editor to remount and revert all content inside the block to its state at page load. A hard refresh restores the correct saved content.
This issue is particularly annoying when paired with auto-save since it will revert states causing editors to lose work.
Disclaimer: I also added a "Root Cause" and a "Workaround" section but these were provided by AI so take it with a grain of salt. I tested the workaround and it does indeed work.
Link to the code that reproduces this issue
https://github.com/lorand-kis/payload-issue-lexical-block-with-array-field
Reproduction Steps
Steps to reproduce
Expected behavior
The editor retains the saved content without remounting.
Actual behavior
The outer richText editor remounts after every save and all content inside affected blocks reverts to the page-load state.
Root cause
Three bugs interact to produce this:
On the client,
removeEmptyArrayValuesmarks empty array fields withdisableFormData: true. When the Lexical editor serializes the block node to JSON, those fields are omitted — so the saved state contains{ body: "..." }with no subitems key. On the server, afterRead/promise.js:351 unconditionally normalizes absent array fields to[]. The PATCH andgetFormStateresponses therefore contain{ body: "...", subitems: [] }.After every save,
MERGE_SERVER_STATEupdates the outerrichTextfield with the server's Lexical state.handleInitialValueChangeinField.jscompares the client state (no subitems key) against the server state(subitems: [])usingdequal. The structural difference makesdequalreturnfalse, causingsetRerenderProviderKey(new Date())to fire and remount the entire outer Lexical editor.On remount, the block component's
useStatelazy initializer runs. It looks up current field values informDatausing afieldName in formDatacheck. Because nested fields are stored under dot-notation keys (e.g.items.0.body), this top-level check always fails. The initializer falls back toinitialLexicalFormState— the form state captured at page load — and all block fields render with their original values.Workaround
Add a collection-level
afterReadhook that strips the empty array keys from the affected block nodes in the Lexical JSON after Payload's normalization runs, so the server response matches the client state anddequalpasses.Which area(s) are affected?
plugin: richtext-lexical
Environment Info