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

  1. Move the dispatchCommand/update call outside the editor.read() callback — read first, then dispatch after read returns.
  2. If asynchrony is acceptable, wrap the dispatch in queueMicrotask(() => editor.dispatchCommand(...)) so it runs after the read context unwinds.
  3. Restructure the code so mutations use editor.update(() => ...) instead of mixing read and dispatch.
  4. 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

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


AI-assisted analysis of facebook/lexical@76a22dcba9 (2026-08-31). Data as JSON: /api/errors/711c49798e928710. Report an issue: GitHub.