Skip to content

[lexical] Bug Fix: EditorState.clone() keeps the _parsed flag - #8989

Merged
etrepum merged 1 commit into
facebook:mainfrom
LeSingh1:fix/editorstate-clone-parsed
Aug 9, 2026
Merged

[lexical] Bug Fix: EditorState.clone() keeps the _parsed flag#8989
etrepum merged 1 commit into
facebook:mainfrom
LeSingh1:fix/editorstate-clone-parsed

Conversation

@LeSingh1

@LeSingh1 LeSingh1 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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
#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:

// 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 #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)

## 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)
```
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

@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.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 9, 2026
@etrepum
etrepum added this pull request to the merge queue Aug 9, 2026
Merged via the queue into facebook:main with commit a8ba4de Aug 9, 2026
44 of 46 checks passed
@etrepum etrepum mentioned this pull request Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants