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.
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.
Function with read-only access to this editor state. Can call $ functions.
Editor instance to use for context
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.
Optional selection to use instead of the current selection
A new EditorState instance
const currentState = editor . getEditorState ();
const clonedState = currentState . clone ();
toJSON
toJSON (): SerializedEditorState
Serializes the EditorState to a JSON-serializable object.
A JSON-serializable representation of the editor state The serialized root node and its descendants
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
Returns true if the editor state only contains an empty root node with no selection.
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:
Node Map - A map of all nodes by their keys
Selection - The current selection state (or null)
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' );
}
});