or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

caret-system.mdcommand-system.mdeditor-management.mdindex.mdnode-system.mdselection-system.mdstate-management.mdutilities-helpers.md
tile.json

state-management.mddocs/

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.