Skip to main content
The EditorState represents an immutable snapshot of the editor’s content at a point in time. It contains the node tree and selection state.

Overview

EditorState is the core data structure in Lexical. It’s immutable and can be safely serialized, cloned, and passed around. All mutations to the editor happen by creating a new EditorState.

Type Guards

$isEditorState

function $isEditorState(x: unknown): x is EditorState
Type guard that returns true if the argument is an EditorState.
x
unknown
required
Value to test
result
boolean
True if x is an EditorState instance
import { $isEditorState } from 'lexical';

if ($isEditorState(value)) {
  // value is EditorState
  const json = value.toJSON();
}

Methods

read

read<V>(callbackFn: () => V, options?: EditorStateReadOptions): V
Executes a read-only function with this EditorState as the active state.
callbackFn
() => V
required
Function with read-only access to this editor state. Can call $ functions.
options
EditorStateReadOptions
result
V
The return value from callbackFn
const editorState = editor.getEditorState();

const textContent = editorState.read(() => {
  const root = $getRoot();
  return root.getTextContent();
});

clone

clone(selection?: null | BaseSelection): EditorState
Creates a new EditorState with the same node map and optionally a different selection.
selection
BaseSelection | null
Optional selection to use instead of the current selection
state
EditorState
A new EditorState instance
const currentState = editor.getEditorState();
const clonedState = currentState.clone();

toJSON

toJSON(): SerializedEditorState
Serializes the EditorState to a JSON-serializable object.
json
SerializedEditorState
A JSON-serializable representation of the editor state
const editorState = editor.getEditorState();
const json = editorState.toJSON();
const stringified = JSON.stringify(json);

// Later, restore the state
const parsed = JSON.parse(stringified);
const restoredState = editor.parseEditorState(parsed);
editor.setEditorState(restoredState);

isEmpty

isEmpty(): boolean
Returns true if the editor state only contains an empty root node with no selection.
empty
boolean
True if the editor state is empty
const editorState = editor.getEditorState();

if (editorState.isEmpty()) {
  console.log('Editor is empty');
}

Working with EditorState

Getting the Current State

The current EditorState is available through the editor:
const currentState = editor.getEditorState();

Reading from EditorState

You can read from an EditorState without needing an active editor update:
const editorState = editor.getEditorState();

editorState.read(() => {
  const root = $getRoot();
  const selection = $getSelection();
  
  console.log('Text:', root.getTextContent());
  console.log('Selection:', selection);
});

Setting EditorState

You can imperatively set a new EditorState:
const newState = editor.parseEditorState(savedStateJSON);
editor.setEditorState(newState);

Update Listeners

EditorState is passed to update listeners:
editor.registerUpdateListener(({ editorState, prevEditorState }) => {
  editorState.read(() => {
    // Access the new state
    const root = $getRoot();
  });
  
  prevEditorState.read(() => {
    // Access the previous state if needed
  });
});

Serialization

Saving State

EditorState can be serialized to JSON for storage:
function saveEditorState() {
  const state = editor.getEditorState();
  const json = state.toJSON();
  localStorage.setItem('editorState', JSON.stringify(json));
}

Loading State

Deserialize and restore a saved state:
function loadEditorState() {
  const saved = localStorage.getItem('editorState');
  if (saved) {
    const parsed = JSON.parse(saved);
    const state = editor.parseEditorState(parsed);
    editor.setEditorState(state);
  }
}

State Structure

An EditorState contains:
  1. Node Map - A map of all nodes by their keys
  2. Selection - The current selection state (or null)
  3. Read-only Flag - Whether the state is read-only (becomes true after reconciliation)
type SerializedEditorState = {
  root: SerializedRootNode;
};

Best Practices

Don’t Store Node References

Node references from one EditorState should not be used in another:
// ❌ Don't do this
let savedNode;
editor.update(() => {
  savedNode = $getRoot().getFirstChild();
});

editor.update(() => {
  // savedNode is from a different EditorState!
  savedNode.remove(); // This will error
});

// ✅ Do this instead
let savedNodeKey;
editor.update(() => {
  savedNodeKey = $getRoot().getFirstChild()?.getKey();
});

editor.update(() => {
  const node = $getNodeByKey(savedNodeKey);
  if (node) {
    node.remove();
  }
});

Use read() for Safe Access

When working with an EditorState outside of an update, use read():
// ✅ Good
const state = editor.getEditorState();
state.read(() => {
  const root = $getRoot();
  // Safe to use $ functions
});

// ❌ Bad - $ functions won't work
const state = editor.getEditorState();
const root = $getRoot(); // Error: no active editor state

Compare States

You can compare EditorStates by reference:
editor.registerUpdateListener(({ editorState, prevEditorState }) => {
  if (editorState !== prevEditorState) {
    console.log('State changed');
  }
});