facebook/lexical · warning
updateEditorSync: an editor update (e.g. a command listener
Error message
updateEditorSync: an editor update (e.g. a command listener that mutates the editor) ran while a read-only context was on the stack. This most commonly happens when a command is dispatched from inside editor.read(). The update has been deferred to a fresh writable update so it still applies, but dispatching mutations from a read-only context is an anti-pattern — dispatch after editor.read() returns, or via queueMicrotask.
What it means
updateEditorSync detected that an editor mutation (typically a command listener that mutates the editor) ran while a read-only context (editor.read()) was on the stack. If inlined directly it would mutate a frozen editor state and throw 'Cannot call set() on a frozen Lexical node map', which gets routed to editor._onError and silently dropped. Lexical instead defers the work through $beginUpdate into a fresh writable update so it still applies, and warns that dispatching mutations from a read-only context is an anti-pattern.
Source
Thrown at packages/lexical/src/LexicalUpdates.ts:1313
* within an update
*/
export function updateEditorSync(
editor: LexicalEditor,
updateFn: () => void,
options?: EditorUpdateOptions,
): void {
if (activeEditor === editor && options === undefined) {
if (isCurrentlyReadOnlyMode()) {
// We are nominally "inside an update" for this editor, but the active
// context is read-only (e.g. a command dispatched from inside
// editor.read(), or a force-commit read on the stack). Running updateFn
// inline here would mutate the frozen active editor state and throw
// "Cannot call set() on a frozen Lexical node map" — an error that gets
// routed to editor._onError rather than rethrown, so the mutation is
// silently dropped. Route through $beginUpdate instead, which starts a
// fresh writable update, so the work actually applies.
if (__DEV__) {
console.warn(
`updateEditorSync: an editor update (e.g. a command listener that ` +
`mutates the editor) ran while a read-only context was on the ` +
`stack. This most commonly happens when a command is dispatched ` +
`from inside editor.read(). The update has been deferred to a ` +
`fresh writable update so it still applies, but dispatching ` +
`mutations from a read-only context is an anti-pattern — dispatch ` +
`after editor.read() returns, or via queueMicrotask.`,
);
}
$beginUpdate(editor, updateFn, options);
} else {
updateFn();
}
} else {
$beginUpdate(editor, updateFn, options);
}
}
View on GitHub (pinned to 76a22dcba9)
Solutions
- Move the dispatchCommand/update call outside the editor.read() callback — read first, then dispatch after read returns.
- If asynchrony is acceptable, wrap the dispatch in queueMicrotask(() => editor.dispatchCommand(...)) so it runs after the read context unwinds.
- Restructure the code so mutations use editor.update(() => ...) instead of mixing read and dispatch.
- Use editor.read('pending', ...) or read('latest', ...) semantics appropriately rather than dispatching mid-read.
Example fix
// before
editor.read(() => {
if (needFix()) {
editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold');
}
});
// after
editor.read(() => {
if (needFix()) {
queueMicrotask(() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold'));
}
}); Defensive patterns
Strategy: try-catch
Try / catch
try {
editor.read(() => {
// read-only work only; dispatch AFTER read returns
});
if (shouldDispatch) editor.dispatchCommand(MY_COMMAND, payload);
} catch (e) {
editor._onError?.(e);
} Prevention
- Never dispatch commands or call editor.update() inside editor.read() callbacks.
- Use queueMicrotask/setTimeout to defer dispatches triggered from read paths.
- Keep read functions pure: return data, let the caller decide on mutations.
- Listen for this dev warning in tests to catch read/dispatch mixing early.
When it happens
Trigger: Calling editor.dispatchCommand (or otherwise triggering updateEditorSync) from inside editor.read(...) where a registered command listener mutates the editor; any update attempt while a read-only context is active on the update stack.
Common situations: Dispatching a command inside an editor.read() block (e.g. reading state then immediately dispatching a formatting command); event handlers that call read() and then update synchronously; click/selection handlers performing read-then-dispatch.
Related errors
- [lexical] duplicate DOMImportRule name "${rule.name}" — keep
- message
- TableNode: hasHorizontalScroll is active but theme.tableScro
- ${name} must implement static "${method}" method
- ${name} should implement "importJSON" method to ensure JSON
AI-assisted analysis of facebook/lexical@76a22dcba9 (2026-08-31).
Data as JSON: /api/errors/711c49798e928710.
Report an issue: GitHub.