Lexical blocks: Save Draft submits stale fields while a child form-state response is pending (3.89.0)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 45/100
Research direction
Start with the five-file characterization test in the linked gist and trace the child Form's debounced getFormState call, BlockComponent's setFields update, RichText's deferred parent useField.setValue, and the native SaveDraftButton submit path. Reproduce the ordering gap, then investigate how pending child responses and the editor-to-Form update can be coordinated before the parent Form snapshots fields. Done means the native save includes both updated captions while block B's response is pending, with regression coverage for this ordering.
Written by the indexing model from the issue text.
Description
Describe the Bug
In Payload 3.89.0, the enabled native Save Draft button can submit an earlier Lexical block value while a later edit in another block is still waiting for its nested Form's form-state response.
A deterministic component-level reproduction mounts the public RichTextField and BlocksFeatureClient, real Lexical BlockNodes/BlockComponents, their nested Payload Forms and TextFields, a parent document Form, and the native SaveDraftButton. No editor, field, node, provider, or submit implementation is patched or replaced.
Block A is edited and allowed to reach the parent document Form first. The parent is genuinely modified, so Save Draft is enabled in update mode. Block B's actual input is then edited, but its local form-state transport response is held. Clicking the native button sends a draft PATCH containing A's updated caption and B's previous caption, without waiting for B's response.
After releasing that response and letting the native block/editor/idle propagation complete, another native Save Draft sends both updated captions.
Expected: saving should either coordinate pending child-block changes before taking the document snapshot, or keep the save action unavailable until those changes have reached the document Form.
Actual: an enabled Save Draft completes with the earlier document snapshot while the later child-block change is pending.
Link to the code that reproduces this issue
https://gist.github.com/Nijat-Hamid/b38b76255255bf31902d5425913c6231
The reproduction contains only synthetic captions and minimal test configuration. Save its five files together in a new empty directory, use Node.js 24.18.0 or later, and run npm install followed by npm test. It also passed after a separate fresh npm install --ignore-scripts --no-audit --no-fund; it does not rely on a pre-existing application's dependency installation.
Reproduction Steps
-
Run the linked characterization test. It seeds two instances of a minimal Lexical block with one text
captionfield each. -
Edit block A's real input to
First A caption edit. Complete its real child Form debounce, the local form-state response, BlockComponent node update, and outer RichText idle callback. Assert the parent is modified and holds A's updated value. -
Edit block B's distinct real input to
Second B caption edit. Let the real child Form invoke the localform-statetransport, but hold that response unresolved. The test also checks that A's input did not change. -
Click the actual enabled
#action-save-draftbutton. Capture its native PATCH FormData and inspect_payload. The caption projection is:[First A caption edit, Original B caption] -
Release B's form-state response, then let the actual BlockComponent
setFieldsupdate and outer RichText idle callback finish. Click native Save Draft again. Its next_payloadcontains:[First A caption edit, Second B caption edit]
The test intentionally passes when it observes this ordering gap; that is a characterization, not the desired fixed behavior. The child response is deferred to make the ordering deterministic, not to implement an application workaround.
Scope of the reproduction
- The public UI/editor path and real input/button interactions run in jsdom. CSS is inert; normal-shaped browser observers and Next navigation context values are supplied by the harness.
- Only server-function/fetch transport is synthetic: form-state returns the supplied field state, preference lookups return local 404s, and draft PATCHes return synthetic HTTP 200 responses. Real sockets and HTTP are blocked.
- No database, CMS server, environment file, account, or real content is involved. This does not establish a production data-loss incident.
- The complete hosted Edit view's
onChange/onSave, real server normalization/validation, database persistence/reload, Autosave and Publish are not tested. No claim is made that those other save paths were reproduced or fixed.
Source trace in the pinned packages
The child Form's debounced onChange awaits getFormState; BlockComponent then schedules editor.update(() => node.setFields(...)). The outer RichText Field defers its parent useField.setValue through useRunDeprioritized (idle timeout 500ms). Native Save Draft invokes the parent Form.submit, which snapshots its current fields before its beforeSubmit callbacks.
I could not find a documented public barrier that waits for both pending child Form responses and the outer RichText-to-Form bridge before that snapshot. An editor-state read alone would not await the child Form's request. If there is a supported coordination hook I have missed, guidance would be appreciated.
Possibly adjacent: #18425, but this report concerns nested Lexical block values in the outgoing document save, rather than late condition/validation metadata merging.
Which area(s) are affected?
area: ui; plugin: richtext-lexical
Environment Info
Payload / @payloadcms/ui / @payloadcms/next / @payloadcms/richtext-lexical: 3.89.0
@payloadcms/translations: 3.89.0
Lexical (installed dependency): 0.41.0
Next.js: 16.3.5
React / React DOM: 19.3.0
Node.js: 24.18.0
Vitest: 5.0.0
jsdom: 30.0.1
Vite: 8.1.5
These are the tested versions, not a claim about the latest v3/v4 release. No package patch or workaround has been applied.
- Dominant language
- TypeScript
- Stars
- 45.3k
- Forks
- 4.3k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 299
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from payloadcms/payload
-
WhereBuilder throws "Cannot read properties of undefined (reading 'value')" when the list URL filters on a field that is not filterablePossibly taken A pull request linked to this issue is open or already merged. OpenBug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#19002 · 1 comment ·
Maintainers usually reply within 1 day
-
Regression: `findByID` with `disableErrors: true` throws `NotFound` for null/empty ids since 3.91.0Possibly taken @Krrishkumar125 claimed this 4 days ago. Openarea: core Bug invalid-reproduction status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
payloadcms/payload#18935 · 1 comment ·
Maintainers usually reply within 1 day
-
bug: Local API call with req resets req.fallbackLocale to defaultLocalePossibly taken A pull request linked to this issue is open or already merged. Openarea: core Bug created-by: Contributor
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
payloadcms/payload#18768 ·
Maintainers usually reply within 1 day
-
[plugin-multi-tenant] Infinite redirect loop with custom tenantFieldName and isGlobal collectionsPossibly taken A pull request linked to this issue is open or already merged. OpenBug plugin: multi-tenant status: needs-triage v3
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
payloadcms/payload#18690 ·
Maintainers usually reply within 1 day
-
Global update drops overrideAccess, so Local API updateGlobal fails filterOptions validation (3.90 regression)Possibly taken @PINYOPATTANAWASANPORN claimed this 12 days ago. Openarea: core Bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
payloadcms/payload#18674 · 2 comments · 1 reaction ·
Maintainers usually reply within 1 day
All issues in payloadcms/payload
Similar issues
-
freshness freshness:observed-only
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
thoroc/context-radar#282 ·
-
bug P2 rust
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
area: web
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
gabe-santana/arkdraw#25 ·
-
bug require-triage
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
expressjs/expressjs.com#2549 · 1 reaction ·
Maintainers usually reply within 1 day
-
bug good first issue hacktoberfest
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
codercops/discord-bot#101 · 1 comment ·
Maintainers usually reply within 1 day