0
# State Management
1
2
Immutable editor state management with node state capabilities for advanced use cases. Lexical's state system provides predictable state transitions and efficient updates through double-buffering and immutable data structures.
3
4
## Capabilities
5
6
### Editor State
7
8
Core editor state class representing the immutable document state at any point in time.
9
10
```typescript { .api }
11
/**
12
* Immutable editor state containing the document tree and selection
13
*/
14
interface EditorState {
15
/** Read from the editor state */
16
read<T>(callbackFn: () => T): T;
17
/** Clone the editor state with optional new selection */
18
clone(selection?: BaseSelection): EditorState;
19
/** Export editor state to JSON */
20
toJSON(): SerializedEditorState;
21
/** Check if state is empty */
22
isEmpty(): boolean;
23
}
24
25
/**
26
* Serialized representation of editor state
27
*/
28
interface SerializedEditorState {
29
root: SerializedRootNode;
30
}
31
32
/**
33
* Options for reading editor state
34
*/
35
interface EditorStateReadOptions {
36
editor?: LexicalEditor;
37
}
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import { createEditor } from "lexical";
44
45
const editor = createEditor();
46
47
// Get current editor state
48
const editorState = editor.getEditorState();
49
50
// Read from editor state
51
const textContent = editorState.read(() => {
52
const root = $getRoot();
53
return root.getTextContent();
54
});
55
56
// Clone editor state
57
const clonedState = editorState.clone();
58
59
// Serialize editor state
60
const serialized = JSON.stringify(editorState.toJSON());
61
62
// Parse serialized state
63
const parsedState = editor.parseEditorState(serialized);
64
editor.setEditorState(parsedState);
65
```
66
67
### Editor State Updates
68
69
Methods for updating and managing editor state changes on the editor instance.
70
71
```typescript { .api }
72
interface LexicalEditor {
73
/** Get the current editor state */
74
getEditorState(): EditorState;
75
76
/** Set a new editor state */
77
setEditorState(editorState: EditorState, options?: EditorSetOptions): void;
78
79
/** Parse a serialized editor state string */
80
parseEditorState(maybeStringifiedEditorState: string): EditorState;
81
82
/** Perform an update to create a new editor state */
83
update(updateFn: () => void, options?: EditorUpdateOptions): void;
84
85
/** Read from the current editor state */
86
read<T>(readFn: () => T): T;
87
}
88
89
interface EditorSetOptions {
90
/** Tag to identify the update */
91
tag?: string;
92
}
93
94
interface EditorUpdateOptions {
95
/** Tag to identify the update */
96
tag?: string;
97
/** Called when the update is applied to the DOM */
98
onUpdate?: () => void;
99
/** Skip node transforms for this update */
100
skipTransforms?: boolean;
101
/** Disable DOM updates (discrete mode) */
102
discrete?: boolean;
103
}
104
```
105
106
### Node State System
107
108
Advanced state management system for attaching custom state to individual nodes.
109
110
```typescript { .api }
111
/**
112
* Create a state configuration for attaching state to nodes
113
* @param config - State value configuration
114
* @returns State configuration object
115
*/
116
function createState<T>(config: StateValueConfig<T>): StateConfig<T>;
117
118
/**
119
* Create shared node state that can be accessed across nodes
120
* @param defaultValue - Default value for the shared state
121
* @returns Shared state configuration
122
*/
123
function createSharedNodeState<T>(defaultValue: T): StateConfig<T>;
124
125
/**
126
* Get state value from a node
127
* @param node - Node to get state from
128
* @param stateConfig - State configuration
129
* @returns Current state value
130
*/
131
function $getState<T>(node: LexicalNode, stateConfig: StateConfig<T>): T;
132
133
/**
134
* Set state value on a node
135
* @param node - Node to set state on
136
* @param stateConfig - State configuration
137
* @param value - New state value or updater function
138
*/
139
function $setState<T>(
140
node: LexicalNode,
141
stateConfig: StateConfig<T>,
142
value: StateValueOrUpdater<T>
143
): void;
144
145
/**
146
* Get state change between two editor states
147
* @param node - Node to check state for
148
* @param stateConfig - State configuration
149
* @param prevEditorState - Previous editor state
150
* @returns State change information
151
*/
152
function $getStateChange<T>(
153
node: LexicalNode,
154
stateConfig: StateConfig<T>,
155
prevEditorState: EditorState
156
): T | null;
157
158
/**
159
* Get writable node state for modifications
160
* @param node - Node to get writable state for
161
* @param stateConfig - State configuration
162
* @returns Writable state value
163
*/
164
function $getWritableNodeState<T>(
165
node: LexicalNode,
166
stateConfig: StateConfig<T>
167
): T;
168
```
169
170
### Node State Types and Interfaces
171
172
Type definitions for node state system.
173
174
```typescript { .api }
175
/**
176
* State configuration class for managing node state
177
*/
178
abstract class StateConfig<T = unknown> {
179
/** Get state key identifier */
180
abstract getKey(): string;
181
/** Get default state value */
182
abstract getDefaultValue(): T;
183
/** Validate and normalize state value */
184
abstract normalizeValue(value: T): T;
185
}
186
187
/**
188
* Configuration for state values
189
*/
190
interface StateValueConfig<T> {
191
/** Default value for the state */
192
defaultValue: T;
193
/** Key identifier for the state */
194
key?: string;
195
/** Optional validator function */
196
validator?: (value: T) => boolean;
197
/** Optional normalizer function */
198
normalizer?: (value: T) => T;
199
}
200
201
/**
202
* Type for state values or updater functions
203
*/
204
type StateValueOrUpdater<T> = T | ((prevValue: T) => T);
205
206
/**
207
* Generic value or updater function type
208
*/
209
type ValueOrUpdater<T> = T | ((prevValue: T) => T);
210
211
/**
212
* JSON representation of node state
213
*/
214
interface NodeStateJSON {
215
[key: string]: unknown;
216
}
217
218
/**
219
* State configuration key type
220
*/
221
type StateConfigKey = string;
222
223
/**
224
* State configuration value type
225
*/
226
type StateConfigValue<T = unknown> = StateConfig<T>;
227
228
/**
229
* Any state configuration type
230
*/
231
type AnyStateConfig = StateConfig<any>;
232
```
233
234
**Usage Examples:**
235
236
```typescript
237
import {
238
createState,
239
createSharedNodeState,
240
$getState,
241
$setState,
242
$createTextNode
243
} from "lexical";
244
245
// Create state configurations
246
const counterState = createState<number>({
247
defaultValue: 0,
248
key: 'counter'
249
});
250
251
const metadataState = createState<{ tags: string[]; priority: number }>({
252
defaultValue: { tags: [], priority: 0 },
253
validator: (value) => Array.isArray(value.tags),
254
normalizer: (value) => ({
255
...value,
256
priority: Math.max(0, Math.min(10, value.priority))
257
})
258
});
259
260
// Create shared state
261
const themeState = createSharedNodeState<'light' | 'dark'>('light');
262
263
// Use state in editor updates
264
editor.update(() => {
265
const textNode = $createTextNode('Hello');
266
267
// Set state on node
268
$setState(textNode, counterState, 5);
269
$setState(textNode, metadataState, {
270
tags: ['important', 'draft'],
271
priority: 8
272
});
273
274
// Get state from node
275
const count = $getState(textNode, counterState); // 5
276
const metadata = $getState(textNode, metadataState);
277
278
// Update state with function
279
$setState(textNode, counterState, (prev) => prev + 1);
280
281
// Use shared state
282
$setState(textNode, themeState, 'dark');
283
});
284
285
// Check state changes in update listener
286
editor.registerUpdateListener(({ editorState, prevEditorState }) => {
287
editorState.read(() => {
288
const nodes = $nodesOfType(TextNode);
289
nodes.forEach(node => {
290
const stateChange = $getStateChange(node, counterState, prevEditorState);
291
if (stateChange !== null) {
292
console.log('Counter state changed:', stateChange);
293
}
294
});
295
});
296
});
297
```
298
299
### Update Parsing and Serialization
300
301
Utilities for working with serialized nodes and editor state in updates.
302
303
```typescript { .api }
304
/**
305
* Parse a serialized node back to a node instance
306
* @param serializedNode - Serialized node data
307
* @returns Node instance
308
*/
309
function $parseSerializedNode(serializedNode: SerializedLexicalNode): LexicalNode;
310
311
/**
312
* Check if editor is currently in read-only mode
313
* @returns True if in read-only mode
314
*/
315
function isCurrentlyReadOnlyMode(): boolean;
316
```
317
318
### Advanced State Patterns
319
320
Common patterns for working with editor and node state.
321
322
```typescript
323
// State-based node behavior
324
const expandedState = createState<boolean>({
325
defaultValue: false,
326
key: 'expanded'
327
});
328
329
// Custom node with state-dependent behavior
330
class CollapsibleNode extends ElementNode {
331
isExpanded(): boolean {
332
return $getState(this, expandedState);
333
}
334
335
toggleExpanded(): void {
336
$setState(this, expandedState, (prev) => !prev);
337
}
338
339
createDOM(config: EditorConfig): HTMLElement {
340
const element = super.createDOM(config);
341
const isExpanded = this.isExpanded();
342
element.classList.toggle('collapsed', !isExpanded);
343
return element;
344
}
345
}
346
347
// Tracking state changes across updates
348
const trackingState = createState<{ lastModified: number; version: number }>({
349
defaultValue: { lastModified: Date.now(), version: 1 }
350
});
351
352
editor.registerUpdateListener(({ editorState, prevEditorState, tags }) => {
353
if (tags.has('user-input')) {
354
editorState.read(() => {
355
const nodes = $nodesOfType(TextNode);
356
nodes.forEach(node => {
357
$setState(node, trackingState, (prev) => ({
358
lastModified: Date.now(),
359
version: prev.version + 1
360
}));
361
});
362
});
363
}
364
});
365
366
// Conditional state application
367
editor.update(() => {
368
const selection = $getSelection();
369
if ($isRangeSelection(selection)) {
370
const nodes = selection.getNodes();
371
nodes.forEach(node => {
372
if ($isTextNode(node)) {
373
const currentCount = $getState(node, counterState);
374
if (currentCount > 10) {
375
$setState(node, metadataState, (prev) => ({
376
...prev,
377
tags: [...prev.tags, 'high-activity']
378
}));
379
}
380
}
381
});
382
}
383
});
384
```
385
386
The state management system in Lexical provides both simple immutable editor state and advanced per-node state capabilities, enabling complex editor behaviors while maintaining predictable state transitions and efficient updates.