CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-lexical

Lexical is an extensible text editor framework that provides excellent reliability, accessible and performance.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

state-management.mddocs/

State Management

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.

Capabilities

Editor State

Core editor state class representing the immutable document state at any point in time.

/**
 * Immutable editor state containing the document tree and selection
 */
interface EditorState {
  /** Read from the editor state */
  read<T>(callbackFn: () => T): T;
  /** Clone the editor state with optional new selection */
  clone(selection?: BaseSelection): EditorState;
  /** Export editor state to JSON */
  toJSON(): SerializedEditorState;
  /** Check if state is empty */
  isEmpty(): boolean;
}

/**
 * Serialized representation of editor state
 */
interface SerializedEditorState {
  root: SerializedRootNode;
}

/**
 * Options for reading editor state
 */
interface EditorStateReadOptions {
  editor?: LexicalEditor;
}

Usage Examples:

import { createEditor } from "lexical";

const editor = createEditor();

// Get current editor state
const editorState = editor.getEditorState();

// Read from editor state
const textContent = editorState.read(() => {
  const root = $getRoot();
  return root.getTextContent();
});

// Clone editor state
const clonedState = editorState.clone();

// Serialize editor state
const serialized = JSON.stringify(editorState.toJSON());

// Parse serialized state
const parsedState = editor.parseEditorState(serialized);
editor.setEditorState(parsedState);

Editor State Updates

Methods for updating and managing editor state changes on the editor instance.

interface LexicalEditor {
  /** Get the current editor state */
  getEditorState(): EditorState;
  
  /** Set a new editor state */
  setEditorState(editorState: EditorState, options?: EditorSetOptions): void;
  
  /** Parse a serialized editor state string */
  parseEditorState(maybeStringifiedEditorState: string): EditorState;
  
  /** Perform an update to create a new editor state */
  update(updateFn: () => void, options?: EditorUpdateOptions): void;
  
  /** Read from the current editor state */
  read<T>(readFn: () => T): T;
}

interface EditorSetOptions {
  /** Tag to identify the update */
  tag?: string;
}

interface EditorUpdateOptions {
  /** Tag to identify the update */
  tag?: string;
  /** Called when the update is applied to the DOM */
  onUpdate?: () => void;
  /** Skip node transforms for this update */
  skipTransforms?: boolean;
  /** Disable DOM updates (discrete mode) */
  discrete?: boolean;
}

Node State System

Advanced state management system for attaching custom state to individual nodes.

/**
 * Create a state configuration for attaching state to nodes
 * @param config - State value configuration
 * @returns State configuration object
 */
function createState<T>(config: StateValueConfig<T>): StateConfig<T>;

/**
 * Create shared node state that can be accessed across nodes
 * @param defaultValue - Default value for the shared state
 * @returns Shared state configuration
 */
function createSharedNodeState<T>(defaultValue: T): StateConfig<T>;

/**
 * Get state value from a node
 * @param node - Node to get state from
 * @param stateConfig - State configuration
 * @returns Current state value
 */
function $getState<T>(node: LexicalNode, stateConfig: StateConfig<T>): T;

/**
 * Set state value on a node
 * @param node - Node to set state on
 * @param stateConfig - State configuration
 * @param value - New state value or updater function
 */
function $setState<T>(
  node: LexicalNode, 
  stateConfig: StateConfig<T>, 
  value: StateValueOrUpdater<T>
): void;

/**
 * Get state change between two editor states
 * @param node - Node to check state for
 * @param stateConfig - State configuration
 * @param prevEditorState - Previous editor state
 * @returns State change information
 */
function $getStateChange<T>(
  node: LexicalNode,
  stateConfig: StateConfig<T>,
  prevEditorState: EditorState
): T | null;

/**
 * Get writable node state for modifications
 * @param node - Node to get writable state for
 * @param stateConfig - State configuration
 * @returns Writable state value
 */
function $getWritableNodeState<T>(
  node: LexicalNode,
  stateConfig: StateConfig<T>
): T;

Node State Types and Interfaces

Type definitions for node state system.

/**
 * State configuration class for managing node state
 */
abstract class StateConfig<T = unknown> {
  /** Get state key identifier */
  abstract getKey(): string;
  /** Get default state value */
  abstract getDefaultValue(): T;
  /** Validate and normalize state value */
  abstract normalizeValue(value: T): T;
}

/**
 * Configuration for state values
 */
interface StateValueConfig<T> {
  /** Default value for the state */
  defaultValue: T;
  /** Key identifier for the state */
  key?: string;
  /** Optional validator function */
  validator?: (value: T) => boolean;
  /** Optional normalizer function */
  normalizer?: (value: T) => T;
}

/**
 * Type for state values or updater functions
 */
type StateValueOrUpdater<T> = T | ((prevValue: T) => T);

/**
 * Generic value or updater function type
 */
type ValueOrUpdater<T> = T | ((prevValue: T) => T);

/**
 * JSON representation of node state
 */
interface NodeStateJSON {
  [key: string]: unknown;
}

/**
 * State configuration key type
 */
type StateConfigKey = string;

/**
 * State configuration value type
 */
type StateConfigValue<T = unknown> = StateConfig<T>;

/**
 * Any state configuration type
 */
type AnyStateConfig = StateConfig<any>;

Usage Examples:

import { 
  createState, 
  createSharedNodeState,
  $getState, 
  $setState,
  $createTextNode 
} from "lexical";

// Create state configurations
const counterState = createState<number>({
  defaultValue: 0,
  key: 'counter'
});

const metadataState = createState<{ tags: string[]; priority: number }>({
  defaultValue: { tags: [], priority: 0 },
  validator: (value) => Array.isArray(value.tags),
  normalizer: (value) => ({
    ...value,
    priority: Math.max(0, Math.min(10, value.priority))
  })
});

// Create shared state
const themeState = createSharedNodeState<'light' | 'dark'>('light');

// Use state in editor updates
editor.update(() => {
  const textNode = $createTextNode('Hello');
  
  // Set state on node
  $setState(textNode, counterState, 5);
  $setState(textNode, metadataState, { 
    tags: ['important', 'draft'], 
    priority: 8 
  });
  
  // Get state from node
  const count = $getState(textNode, counterState); // 5
  const metadata = $getState(textNode, metadataState);
  
  // Update state with function
  $setState(textNode, counterState, (prev) => prev + 1);
  
  // Use shared state
  $setState(textNode, themeState, 'dark');
});

// Check state changes in update listener
editor.registerUpdateListener(({ editorState, prevEditorState }) => {
  editorState.read(() => {
    const nodes = $nodesOfType(TextNode);
    nodes.forEach(node => {
      const stateChange = $getStateChange(node, counterState, prevEditorState);
      if (stateChange !== null) {
        console.log('Counter state changed:', stateChange);
      }
    });
  });
});

Update Parsing and Serialization

Utilities for working with serialized nodes and editor state in updates.

/**
 * Parse a serialized node back to a node instance
 * @param serializedNode - Serialized node data
 * @returns Node instance
 */
function $parseSerializedNode(serializedNode: SerializedLexicalNode): LexicalNode;

/**
 * Check if editor is currently in read-only mode
 * @returns True if in read-only mode
 */
function isCurrentlyReadOnlyMode(): boolean;

Advanced State Patterns

Common patterns for working with editor and node state.

// State-based node behavior
const expandedState = createState<boolean>({
  defaultValue: false,
  key: 'expanded'
});

// Custom node with state-dependent behavior
class CollapsibleNode extends ElementNode {
  isExpanded(): boolean {
    return $getState(this, expandedState);
  }
  
  toggleExpanded(): void {
    $setState(this, expandedState, (prev) => !prev);
  }
  
  createDOM(config: EditorConfig): HTMLElement {
    const element = super.createDOM(config);
    const isExpanded = this.isExpanded();
    element.classList.toggle('collapsed', !isExpanded);
    return element;
  }
}

// Tracking state changes across updates
const trackingState = createState<{ lastModified: number; version: number }>({
  defaultValue: { lastModified: Date.now(), version: 1 }
});

editor.registerUpdateListener(({ editorState, prevEditorState, tags }) => {
  if (tags.has('user-input')) {
    editorState.read(() => {
      const nodes = $nodesOfType(TextNode);
      nodes.forEach(node => {
        $setState(node, trackingState, (prev) => ({
          lastModified: Date.now(),
          version: prev.version + 1
        }));
      });
    });
  }
});

// Conditional state application
editor.update(() => {
  const selection = $getSelection();
  if ($isRangeSelection(selection)) {
    const nodes = selection.getNodes();
    nodes.forEach(node => {
      if ($isTextNode(node)) {
        const currentCount = $getState(node, counterState);
        if (currentCount > 10) {
          $setState(node, metadataState, (prev) => ({
            ...prev,
            tags: [...prev.tags, 'high-activity']
          }));
        }
      }
    });
  }
});

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.

docs

caret-system.md

command-system.md

editor-management.md

index.md

node-system.md

selection-system.md

state-management.md

utilities-helpers.md

tile.json