From zbeyens. The source code is available on GitHub.

Plate
PlatePliteEditorsTemplates
GitHub16kGitHub
DiscordDiscord
UNPUBLISHED
  • Overview
  • Why This Fork
  • Examples
Walkthroughs
  • Installing Plite
  • Adding Event Handlers
  • Defining Custom Elements
  • Applying Custom Formatting
  • Executing Commands
  • Saving to a Database
  • Canonical Change Substrate
  • Improving Performance
Concepts
  • Interfaces
  • Nodes
  • Locations
  • Transforms
  • Document Changes
  • Commands
  • Editor
  • Extensions
  • Rendering
  • Serializing
  • Normalizing
  • Using TypeScript
  • Roots
  • Document State
  • Editing Behavior
  • Selection And DOM
  • Clipboard And Paste
  • Projection And Overlays
  • Schema
API
  • Anchor API
  • Location API
  • Path API
  • PointEntry API
  • Point API
  • Range API
  • Selection API
  • Location Types APIs
  • Span API
  • Editor
  • Element API
  • NodeEntry API
  • Node API
  • Node Types APIs
  • Text API
  • Debug Value Scrubbing
  • Transforms API
Libraries
  • Plite DOM
  • History Editor API
  • History Extension Setup
  • History
  • Plite History
  • Plite Hyperscript
  • Plite Layout
  • Annotations
  • DOM Coverage Boundaries
  • Editable Component
  • Plite React Event Handling
  • Virtualized Rendering
  • Plite React Hooks
  • React Editor Setup
  • React Editor
  • Plite React
  • Plite Component
  • Plite Yjs
  • Plite
General
  • Migration
  • Contributing
  • Docs Proof Map
  • FAQ
  • Resources

Plite React Hooks

PreviousNext

Read editor state, root state, projections, annotations, widgets, and DOM-aware facts from React.

Use hooks inside a Plite provider when React UI needs editor facts. Prefer the narrowest hook that matches the UI: editor-wide hooks for toolbars, element hooks for rendered nodes, and projection hooks for decorations, annotations, and widgets.

On This Page

  • Editor Hooks
  • Runtime And Root Hooks
  • Element And Node Hooks
  • Projection Hooks
  • DOM Strategy Hooks
  • Annotation Hooks
  • Widget Hooks

Editor Hooks

Virtualized RenderingReact Editor Setup

On This Page

On This PageEditor HooksuseEditor(): EditoruseEditorComposing(): booleanuseEditorFocused(): booleanuseEditorReadOnly(): booleanuseEditorSelection(): Range | nulluseEditorState<T>(selector, options?): TuseEditorRuntimeState<T>(editor, selector, options?): TuseStateFieldValue<T>(field, options?): TuseSetStateField<T>(field): (value, policy?) => voiduseEditorSelector<T>(selector, options?): TusePliteHistory(options?): PliteHistoryControllerRuntime And Root HooksusePliteRuntime(options?): PliteRuntimeValueusePliteRuntimeState<T>(selector, options?): TusePliteRootState<T>(root, selector, options?): TusePliteActiveRoot(): RootKey | undefinedusePliteRootEditor(root?, options?): PliteRootEditorusePliteActiveEditor(): PliteRootEditorusePliteRootChrome(root?, options?): PliteRootChromeControllerusePliteContentRoot(element?, options?): PliteContentRootControllerusePliteChildRoot(element?, slot?): RootKeyusePliteRootEffect(effect, options?)usePliteCommand(command, options?): (input) => booleanElement And Node HooksuseElement(): ElementuseElementPath(): Path | nulluseElementSelected(options?: UseElementSelectedOptions): booleanusePliteNodeRef(nodeKey, options?): (node) => voiduseNodeSelector<T>(selector, equalityFn?, options?): TuseTextSelector<T>(selector, equalityFn?, options?): TuseDecorationSelector<T>(selector, equalityFn?, options?): TProjection HooksusePliteProjectionEntries<T>(nodeKey): readonly PliteProjectionEntry<T>[]usePliteDecorationSource<T>(editor, options): PliteDecorationSource<T>usePliteRangeDecorationSource<T>(editor, options): PliteDecorationSource<T>DOM Strategy HooksuseDOMStrategyVirtualOffset(): numberAnnotation HooksusePliteAnnotationStore<TData, TProjection>(editor, annotations, options?): PliteAnnotationStore<TData, TProjection>usePliteAnnotations<TData, TProjection>(store?): PliteAnnotationSnapshot<TData, TProjection>usePliteAnnotation<TData, TProjection>(id, store?): PliteResolvedAnnotation<TData, TProjection> | nullWidget HooksusePliteWidgetStore<TWidget, TAnnotation>(editor, widgets, options?): PliteWidgetStore<TWidget, TAnnotation>usePliteWidgets<TWidget, TAnnotation>(store): PliteWidgetSnapshot<TWidget, TAnnotation>usePliteWidget<TWidget, TAnnotation>(store, id): PliteResolvedWidget<TWidget, TAnnotation> | null
Build your editor
Production-ready AI template and reusable components.
Get all-access

useEditor(): Editor

Get the current editor object from React context. Use usePliteEditor to create an editor; use useEditor inside descendants that read the provider editor.

useEditorComposing(): boolean

Get whether the editor is currently handling a composition session.

useEditorFocused(): boolean

Get whether the editor is focused. Use this for toolbar UI, not for every rendered node in a large document.

useEditorReadOnly(): boolean

Get whether the current editor is read-only.

useEditorSelection(): Range | null

Get the current editor selection. This hook re-renders when the selection changes, so keep it out of large rendered node trees.

useEditorState<T>(selector, options?): T

Subscribe to a derived editor-state value. The selector runs inside editor.read, so toolbar UI does not need to open a read boundary by hand.

const isBold = useEditorState((state) => {
  return state.marks()?.bold === true;
});
const isBold = useEditorState((state) => {
  return state.marks()?.bold === true;
});

Use options.shouldUpdate to skip commits that cannot affect the selected value.

const selection = useEditorState((state) => state.selection(), {
  shouldUpdate: (change) => Boolean(change?.selectionChanged),
});
const selection = useEditorState((state) => state.selection(), {
  shouldUpdate: (change) => Boolean(change?.selectionChanged),
});

Selectors always call the latest render's function, so they can close over component props without a dependency list.

const matchingText = useEditorState(
  (state) => state.text.string([]).includes(query)
);
const matchingText = useEditorState(
  (state) => state.text.string([]).includes(query)
);

useEditorRuntimeState<T>(editor, selector, options?): T

Subscribe to editor state from an explicit editor instance. Use this for toolbars, containers, or app chrome that receives an editor but is not rendered inside that editor's Plite provider.

const readOnly = useEditorRuntimeState(editor, (state) =>
  state.view.isReadOnly()
);
const readOnly = useEditorRuntimeState(editor, (state) =>
  state.view.isReadOnly()
);

Inside provider descendants, prefer useEditorState. It reads the same state but gets the editor from context.

Commit subscriptions invalidate this selector synchronously. equalityFn and shouldUpdate suppress unnecessary renders. Use a provider selector's explicit deferred option when delayed delivery is an intentional product choice.

useStateFieldValue<T>(field, options?): T

Subscribe to one defineStateField value.

const title = useStateFieldValue(documentTitle);
const title = useStateFieldValue(documentTitle);

The hook only re-renders when that field key appears in change.dirtyStateKeys. Use it for document title, page settings, spellcheck, and other document meta controls.

useSetStateField<T>(field): (value, policy?) => void

Create a setter for one defineStateField value.

const setTitle = useSetStateField(documentTitle);
 
setTitle("Q3 Launch Brief", { history: "new-batch", tags: "title-input" });
const setTitle = useSetStateField(documentTitle);
 
setTitle("Q3 Launch Brief", { history: "new-batch", tags: "title-input" });

The setter writes through editor.update and preserves DOM selection by default. Pass the editor's typed update policy when the app needs history or additional tags; the hook appends its selection-preservation tags.

useEditorSelector<T>(selector, options?): T

Subscribe to a low-level derived editor value.

Use useEditorState for normal app-level editor reads. Use useEditorSelector when you intentionally need the editor object or an installed runtime API. Prefer node-, text-, decoration-, or element-scoped hooks when rendering editor content.

const documentVersion = useEditorSelector(
  (editor) => editor.read.runtime.snapshot().version,
  {
    equalityFn: Object.is,
    shouldUpdate: (commit) =>
      commit === undefined || commit.changed.has("document"),
  }
);
const documentVersion = useEditorSelector(
  (editor) => editor.read.runtime.snapshot().version,
  {
    equalityFn: Object.is,
    shouldUpdate: (commit) =>
      commit === undefined || commit.changed.has("document"),
  }
);

usePliteHistory(options?): PliteHistoryController

Create undo/redo commands and keyboard handling for the active root.

const history = usePliteHistory();
 
return (
  <button disabled={!history.canUndo} onClick={history.undo}>
    Undo
  </button>
);
const history = usePliteHistory();
 
return (
  <button disabled={!history.canUndo} onClick={history.undo}>
    Undo
  </button>
);

Pass options.root to bind history controls to one root. Pass focusPolicy: 'preserve' when undo/redo is controlled from external UI and DOM focus should stay outside the editor. history.root is undefined for the primary document and the root key for an extra root.

Runtime And Root Hooks

Use these when one editor owns multiple roots or external chrome.

  • Runtime hooks read the whole editor runtime.
  • Root state hooks read one root.
  • Root editor hooks return a command-capable editor for one root.

Prefer usePliteRootEditor(root) when UI knows its root. Use usePliteActiveEditor() only for UI that should follow the current selection.

usePliteRuntime(options?): PliteRuntimeValue

Create a React runtime value, or read the nearest runtime from PliteRuntime. Most app code should use Plite directly.

One mounted PliteRuntime owns one editor runtime. Remount the provider with a different React key before supplying a different editor runtime.

usePliteRuntimeState<T>(selector, options?): T

Subscribe to whole-runtime editor state. The selector runs in a read boundary and re-renders only when the selected value changes.

Shared selector options are equalityFn, shouldUpdate, and deferred. Selectors always read the latest render closure. Use shouldUpdate(commit) with commit.changed to skip commits that cannot affect the selected value.

usePliteRootState<T>(root, selector, options?): T

Subscribe to one root's state. The selector skips commits that cannot affect that root.

usePliteActiveRoot(): RootKey | undefined

Read the extra root key that owns the current selection. It returns undefined for the primary document.

usePliteRootEditor(root?, options?): PliteRootEditor

Create a command-capable editor for one root.

Use this for root-specific toolbar or sidebar commands. Pass { readOnly: true } when the editor should only read state. Omit root for the primary document.

usePliteActiveEditor(): PliteRootEditor

Create a command-capable editor for the root that owns the selection.

usePliteRootChrome(root?, options?): PliteRootChromeController

Create root chrome props for mouse interaction outside the editable content, such as margin clicks and drag selection around a root.

const chrome = usePliteRootChrome("body");
 
return <div {...chrome.props}>{children}</div>;
const chrome = usePliteRootChrome("body");
 
return <div {...chrome.props}>{children}</div>;

Pass selection: 'end' for chrome that should place the caret at the end of a root when clicked.

usePliteContentRoot(element?, options?): PliteContentRootController

Resolve a schema-owned child content root and its chrome controller.

Use this inside an element renderer for editable voids or nested editor surfaces.

usePliteChildRoot(element?, slot?): RootKey

Resolve the stable child-root key for an element and slot.

Prefer persisted childRoots[slot] when the child root is part of document data. The runtime fallback is for ephemeral editor islands.

usePliteRootEffect(effect, options?)

Run after Plite flushes mounted roots. Use this when a command or measurement needs the live root editor.

Pass root to target one root. Pass deps for React-style rerun control. Omit deps when the effect should rerun after every React render.

usePliteCommand(command, options?): (input) => boolean

Bind one typed semantic command to the mounted root editor. Command input belongs to the returned dispatcher, so event-time data never becomes hook configuration.

import { editorCommands } from "@platejs/plite";
import { usePliteCommand } from "@platejs/plite-react";
 
const insertBreak = usePliteCommand(editorCommands.insertBreak, {
  focus: "restore-root",
});
 
return <button onClick={() => insertBreak()}>New paragraph</button>;
import { editorCommands } from "@platejs/plite";
import { usePliteCommand } from "@platejs/plite-react";
 
const insertBreak = usePliteCommand(editorCommands.insertBreak, {
  focus: "restore-root",
});
 
return <button onClick={() => insertBreak()}>New paragraph</button>;

Pass focus: 'restore-root' when the command should move focus back to the editor root before running. Pass focus: 'none' to leave focus alone. The default is focus: 'preserve'. Pass root when the command should target a known root instead of the editable or active root.

For an imperative UI callback that is not a semantic command, use the provider editor and React directly. This does not claim replayable command semantics.

const editor = useEditor();
const insertTitle = React.useCallback(() => {
  editor.update.text.insert("Title");
}, [editor]);
const editor = useEditor();
const insertTitle = React.useCallback(() => {
  editor.update.text.insert("Title");
}, [editor]);

Element And Node Hooks

Use these inside rendered editor content or node-local UI.

useElement(): Element

Get the current element object inside an element renderer.

useElementPath(): Path | null

Subscribe to the current path of the rendered element. Use this only for UI that displays or derives live path state during render. Event handlers should usually call editor.api.dom.resolvePath(element) and return early when it is not mounted.

useElementSelected(options?: UseElementSelectedOptions): boolean

Subscribe to whether the current element, or an explicit element path, matches the current selection. The default intersects mode includes text selection inside the element. Use { mode: 'collapsed' } for a collapsed selection and { mode: 'node' } only when the selection is a NodeSelection whose path exactly matches the element. Node-focused asset rings and toolbars should use node; caption or descendant editing UI should use intersects. Pass { at: path } to watch an explicit path.

type UseElementSelectedOptions = {
  at?: Path | null;
  mode?: "intersects" | "collapsed" | "node";
};
type UseElementSelectedOptions = {
  at?: Path | null;
  mode?: "intersects" | "collapsed" | "node";
};

usePliteNodeRef(nodeKey, options?): (node) => void

Bind a custom node element to Plite's node key, path, and DOM lookup maps.

Use this only when replacing Plite's render primitives with a custom DOM shell. Normal renderers should use PliteElement, PliteText, PliteLeaf, or PlitePlaceholder so the required DOM attributes stay attached.

useNodeSelector<T>(selector, equalityFn?, options?): T

Subscribe to a value derived from one mounted node.

Pass options.nodeKey to target a specific node, or call it inside an editor node renderer to use that renderer's runtime target.

useTextSelector<T>(selector, equalityFn?, options?): T

Subscribe to a value derived from one mounted text node.

Pass options.nodeKey to target a specific text node, or call it inside an editor text renderer to use that renderer's runtime target.

useDecorationSelector<T>(selector, equalityFn?, options?): T

Subscribe to decoration/projection data for one mounted runtime target.

Pass options.nodeKey to target a specific runtime node, or call it inside a renderer that already has runtime target context.

Projection Hooks

Use projection hooks when ranges need to be shared across the editor, overlays, sidebars, annotations, or widgets.

usePliteProjectionEntries<T>(nodeKey): readonly PliteProjectionEntry<T>[]

Subscribe to low-level projected range entries for one runtime node. Normal app UI should use decoration sources, annotation stores, or widget stores first. Use projection entries only when custom inline rendering needs the exact projected slices for one mounted node key.

usePliteDecorationSource<T>(editor, options): PliteDecorationSource<T>

Create a provider-owned decoration source from React state.

Use this when ranges are shared across the editor surface, sidebars, toolbars, or other overlay UI. Use Editable.decorate for a simple editor-local callback.

const searchSource = usePliteDecorationSource(editor, {
  id: "search",
  read: ({ snapshot }) => findSearchMatches(snapshot, query),
});
 
return (
  <Plite decorationSources={[searchSource]} editor={editor}>
    <Editable renderSegment={renderSearchMatch} />
  </Plite>
);
const searchSource = usePliteDecorationSource(editor, {
  id: "search",
  read: ({ snapshot }) => findSearchMatches(snapshot, query),
});
 
return (
  <Plite decorationSources={[searchSource]} editor={editor}>
    <Editable renderSegment={renderSearchMatch} />
  </Plite>
);

usePliteRangeDecorationSource<T>(editor, options): PliteDecorationSource<T>

Create a provider-owned decoration source from Plite ranges.

The hook accepts PliteRangeDecorationSourceOptions, reads ranges from the current editor snapshot, and converts them to keyed decorations for the shared projection runtime. Use it for search matches, diagnostics, or highlight data that already lives as Plite ranges.

const searchSource = usePliteRangeDecorationSource(editor, {
  id: "search",
  read: ({ snapshot }) => findSearchRanges(snapshot, query),
});
const searchSource = usePliteRangeDecorationSource(editor, {
  id: "search",
  read: ({ snapshot }) => findSearchRanges(snapshot, query),
});

DOM Strategy Hooks

Use these only when writing DOM-strategy-aware renderers.

useDOMStrategyVirtualOffset(): number

Read the vertical document offset for the current virtualized row.

Use this inside DOM-strategy renderers that paint projected content against absolute document coordinates. Normal editor UI should not need it.

Annotation Hooks

Use annotation hooks for durable anchored ranges such as comments, suggestions, diagnostics, and external review markers.

usePliteAnnotationStore<TData, TProjection>(editor, annotations, options?): PliteAnnotationStore<TData, TProjection>

Create an annotation store for durable anchored ranges such as comments, suggestions, diagnostics, or external review markers.

Pass the store to Plite so Editable, sidebars, and widget UI read one annotation snapshot.

Pass the current annotation array directly. A new array identity refreshes the store automatically. Use revision only for an external mutable source that changes without producing a new array.

const annotations = comments.map((comment) => ({
  anchor: comment.anchor,
  data: comment,
  id: comment.id,
  projection: { tone: comment.tone },
}));
 
const annotationStore = usePliteAnnotationStore(editor, annotations);
 
const externalStore = usePliteAnnotationStore(editor, mutableAnnotations, {
  revision: mutableAnnotationsRevision,
});
const annotations = comments.map((comment) => ({
  anchor: comment.anchor,
  data: comment,
  id: comment.id,
  projection: { tone: comment.tone },
}));
 
const annotationStore = usePliteAnnotationStore(editor, annotations);
 
const externalStore = usePliteAnnotationStore(editor, mutableAnnotations, {
  revision: mutableAnnotationsRevision,
});

usePliteAnnotations<TData, TProjection>(store?): PliteAnnotationSnapshot<TData, TProjection>

Read the current annotation snapshot. Without an explicit store, the hook reads the store from the nearest Plite provider.

usePliteAnnotation<TData, TProjection>(id, store?): PliteResolvedAnnotation<TData, TProjection> | null

Read one annotation by id.

Widget Hooks

Use widget hooks for UI anchored to nodes, selections, or annotations.

usePliteWidgetStore<TWidget, TAnnotation>(editor, widgets, options?): PliteWidgetStore<TWidget, TAnnotation>

Create a widget store for UI anchored to nodes, selections, or annotations.

Pass the current widget array directly. Use revision only when a mutable external source changes without producing a new array.

const widgets = [
  {
    anchor: { annotationId: commentId, type: "annotation" },
    data: { label: "Comment" },
    id: "comment-widget",
  },
];
 
const widgetStore = usePliteWidgetStore(editor, widgets, {
  annotationStore,
});
const widgets = [
  {
    anchor: { annotationId: commentId, type: "annotation" },
    data: { label: "Comment" },
    id: "comment-widget",
  },
];
 
const widgetStore = usePliteWidgetStore(editor, widgets, {
  annotationStore,
});

usePliteWidgets<TWidget, TAnnotation>(store): PliteWidgetSnapshot<TWidget, TAnnotation>

Read every widget in a widget store.

usePliteWidget<TWidget, TAnnotation>(store, id): PliteResolvedWidget<TWidget, TAnnotation> | null

Read one widget by id.