[lexical] Bug Fix: EditorState.clone() keeps the _parsed flag - #8989
Merged
Conversation
## Description `EditorState._parsed` is documented as "True if this EditorState was parsed without running transforms". `parseEditorState` sets it, and `setEditorState` is its only consumer — when the state it is handed has `_parsed`, it dirty-marks every node so registered node transforms (and the hydrate-time shadow-root normalization) run against the freshly parsed content. That is the fix from facebook#7876. `EditorState.clone()` carried `_nodeMap`, the selection and `_slotsUsed`, but not `_parsed`, so the clone always reported `false`. `setEditorState` reads the flag off its *argument*, which means the documented "apply a state without focusing the editor" form silently lost the behaviour: ```js // packages/lexical-website/docs/concepts/editor-state.md // Passing `null` as a selection value to prevent focusing the editor editor.setEditorState(editorState.clone(null)); ``` `setEditorState(parsed)` ran the transforms; `setEditorState(parsed.clone(null))` did not — the same content, applied two documented ways, with different results. A clone describes the same content as the state it came from, so it is still "parsed without running transforms" if the original was. Carry the flag. This does not make transforms re-run later: `cloneEditorState` — the copy `setEditorState` and every commit make internally — still starts a fresh state with `_parsed === false`, which is where the flag is consumed. `clone()` has no callers inside `packages/lexical`; it is a public API. ## Test plan New case in `packages/lexical/src/__tests__/unit/Issue7876Repro.test.ts`, alongside the existing direct-`setEditorState` case it mirrors. ### Before ``` $ npx vitest run packages/lexical/src/__tests__/unit/Issue7876Repro.test.ts FAIL |unit| .../Issue7876Repro.test.ts > Issue facebook#7876: setEditorState triggers transforms on parsed state > text-node transform fires for a cloned parsed state (the documented no-focus form) AssertionError: expected false to be true // Object.is equality - Expected + Received - true + false Test Files 1 failed (1) Tests 1 failed | 4 passed (5) ``` ### After ``` $ npx vitest run packages/lexical/src Test Files 65 passed (65) Tests 1348 passed | 1 skipped (1349) $ npx vitest run packages/lexical-history packages/lexical-yjs packages/lexical-react Test Files 38 passed (38) Tests 285 passed (285) ```
LeSingh1
requested review from
acywatson,
etrepum,
fantactuka,
ivailop7,
potatowagon and
zurfyx
as code owners
August 9, 2026 01:15
|
@LeSingh1 is attempting to deploy a commit to the Meta Open Source Team on Vercel. A member of the Team first needs to authorize it. |
etrepum
approved these changes
Aug 9, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
EditorState._parsedis documented as "True if this EditorState was parsedwithout running transforms".
parseEditorStatesets it, andsetEditorStateis its only consumer — when the state it is handed has
_parsed, it dirty-marksevery node so registered node transforms (and the hydrate-time shadow-root
normalization) run against the freshly parsed content. That is the fix from
#7876.
EditorState.clone()carried_nodeMap, the selection and_slotsUsed, butnot
_parsed, so the clone always reportedfalse.setEditorStatereads theflag off its argument, which means the documented "apply a state without
focusing the editor" form silently lost the behaviour:
setEditorState(parsed)ran the transforms;setEditorState(parsed.clone(null))did not — the same content, applied two documented ways, with different results.
A clone describes the same content as the state it came from, so it is still
"parsed without running transforms" if the original was. Carry the flag.
This does not make transforms re-run later:
cloneEditorState— the copysetEditorStateand every commit make internally — still starts a fresh statewith
_parsed === false, which is where the flag is consumed.clone()has nocallers inside
packages/lexical; it is a public API.Test plan
New case in
packages/lexical/src/__tests__/unit/Issue7876Repro.test.ts,alongside the existing direct-
setEditorStatecase it mirrors.Before
After