Docs
Accessing the Editor

Accessing the Editor

Getting a reference to the editor instance

To do most things with Plate, you'll need to access the editor instance at one point or another. The way you'll do this depends on the context in which you need to access the editor.

From Inside a Plugin

Most often, when you want to extend the functionality of your editor, you'll create a custom Plate plugin. Luckily, plugins are one of the easiest places to access the editor instance.

Inside Event Handlers

Use the first argument of the handler function.

const createMyPlugin = createPluginFactory({
  key: KEY_MY_PLUGIN,
  handlers: {
    onKeyDown: (editor) => (event) => {
      // Do something with editor
    },
    onChange: (editor) => (value) => {
      // Do something with editor
    },
  },
});

Using the Then Option

The purpose of the then option is to access the editor instance in plugin options that normally don't have access to it. Pass a function that takes an editor and returns an object to be merged with the top-level plugin options.

For example, suppose you had this code and wanted to access the editor instance inside deserializeHtml:

const createMyPlugin = createPluginFactory({
  key: KEY_MY_PLUGIN,
  deserializeHtml: {
    // Need editor here
  },
});

You would wrap the deserializeHtml option inside then.

const createMyPlugin = createPluginFactory({
  key: KEY_MY_PLUGIN,
  then: (editor) => ({
    deserializeHtml: {
      // Do something with editor
    },
  }),
});

From a Child of Plate

Use the useEditorRef, useEditorSelector or useEditorState hooks. Which of these hooks you should use depends on when you want your component to re-render in response to changes to editor.

  • useEditorRef - Use a reference to editor that almost never gets replaced. This should be the default choice.
    • Since editor is a mutable object that gets updated by reference, useEditorRef is always sufficient for accessing the editor inside callbacks.
    • useEditorRef will almost never cause your component to re-render, so it's the best choice for performance.
  • useEditorSelector - Subscribe to a specific selector based on editor. This is the most performant option for subscribing to state changes.
    • Example usage: const hasSelection = useEditorSelector((editor) => !!editor.selection, []);
    • When you want your component to re-render in response to a specific change that you're interested in, you can use useEditorSelector to access the relevant property.
    • The selector function is called every time the editor changes (i.e. on every keystroke or selection change), but the component only re-renders when the return value changes.
      • For this to work properly, you should make sure that the return value can be compared using ===. In most cases, this means returning a primitive value, like a number, string or boolean.
      • You can provide a custom equalityFn in the options to useEditorSelector for cases where === isn't sufficient.
    • If the selector function depends on any locally scoped variables, you should include these in the dependency list.
  • useEditorState - Re-render every time the editor changes.
    • Using useEditorState will cause your component to re-render every time the user presses a key or changes the selection.
    • This may cause performance issues for large documents, or when re-rendering is particularly expensive.

You can call these hooks from any React component that is rendered as a descendant of the Plate component, including Plugin Components.

const Toolbar = () => {
  const boldActive = useEditorSelector((editor) => isMarkActive(editor, MARK_BOLD), []);
  // ...
};
 
const Editor = () => (
  <Plate>
    <Toolbar />
    <PlateContent />
  </Plate>
);
const ParagraphElement = ({
  className,
  children,
  ...props
}: PlateElementProps) => {
  const editor = useEditorRef();
 
  const handleClick = useCallback(() => {
    console.info('You clicked on a paragraph, and the editor is ', editor);
  }, [editor]);
 
  return (
    <PlateElement asChild className={className} {...props}>
      <p onClick={handleClick}>{children}</p>
    </PlateElement>
  );
};

One common pattern is to add an effect component as a child of Plate that consumes editor and returns null.

const CustomEffect = () => {
  const editor = useEditorRef();
 
  useEffect(() => {
    const interval = setInterval(() => {
      console.info('The editor is ', editor);
    }, 1000);
 
    return () => clearInterval(interval);
  }, [editor]);
 
  return null;
};
 
export default () => (
  <Plate>
    <CustomEffect />
 
    <PlateContent />
  </Plate>
);

From a Sibling or Ancestor of Plate

If you need to access the editor outside of the Plate component, or if you're working with multiple editors and want to access whichever editor is currently active, you can use a PlateController component. PlateController is an optional component that can be rendered as an ancestor of one or more Plate components. For example, you can render a PlateController at the root of your app.

The PlateController will keep track of which editor is currently active. By default, the first mounted editor inside the PlateController will be used as the active editor (this can be disabled for individual editors by passing primary={false} to the Plate component). After that, every time an editor is focused, that editor becomes active. An editor remains active until another editor is focused or the active editor unmounts.

Inside a PlateController, hooks like useEditorRef and useEditorSelector will return or operate on the currently active editor. If no editor is active, these hooks may return a fallback editor. This can happen under the following circumstances:

  • When no Plate component is mounted inside the PlateController
  • When all mounted Plate components are marked as non-primary
  • Temporarily while PlateContent is in the process of mounting

The fallback editor is designed to ensure that code querying the editor produces sensible default values, the same as if they had been given an empty editor with no plugins. The fallback editor cannot be mutated, and will throw a runtime error if any state-changing operations are applied to it. To check if your component is accessing a real editor or a fallback editor, use the useEditorMounted hook (which returns false for a fallback editor), or check editor.isFallback.

const Toolbar = () => {
  const editor = useEditorState(); // Returns the active editor (or a fallback editor)
  const isMounted = useEditorMounted(); // Returns false if no editor is mounted
  // ...
};
 
const App = () => {
  return (
    <PlateController>
      <Toolbar />
 
      <Plate>
        <PlateContent />
      </Plate>
 
      <Plate>
        <PlateContent />
      </Plate>
    </PlateController>
  );
};

From the Parent of Plate

The editorRef prop is an older, alternative way of accessing the editor outside of Plate, and works best in cases where the editor instance must be accessed in the Plate component's parent. In all other cases, PlateController is the preferred solution (see above).

The ref can be created with useRef, the setter function of useState, or a custom ref callback. It is populated with the editor instance when Plate mounts, and null when Plate unmounts. Since the editor is unavailable during the first render, and may become unavailable if the Plate component unmounts, you must handle the case where editor is null.

const App = () => {
  const editorRef = useRef<PlateEditor | null>(null);
 
  const handleSomeEvent = useCallback(() => {
    const editor = editorRef.current;
 
    // Handle the case where editor is null
    if (editor) {
      // Do something with editor
    }
  }, []);
 
  <Plate editorRef={editorRef}>
    <PlateContent />
  </Plate>
);

Temporary Editor Instance

Sometimes, you'll need to access an editor instance, but not necessarily the same editor instance that is used by the Plate editor itself. Such cases include serializing a Plate value to HTML (either on the client or on the server) and deserializing HTML to produce an initial value for Plate.

In these cases, you can create a temporary editor instance using createPlateEditor({ plugins }). The only requirement is that you pass the same set of plugins to createPlateEditor as you pass to the Plate editor itself.

See the following example to deserialize a HTML value and use it as the initial value of the Plate editor.

// Alternatively, define the plugins inside the React component using useMemo
const plugins = createPlugins([
  // ...
]);
 
const Editor = ({ initialHtml }: { initialHtml: string }) => {
  /**
   * Changing the initialValue after render is not supported, so initialHtml
   * is not included in the useMemo deps.
   */
  const initialValue = useMemo(() => {
    const tmpEditor = createPlateEditor({ plugins });
    return deserializeHtml(tmpEditor, {
      element: initialHtml,
    });
  }, []);
 
  return (
    <Plate plugins={plugins} initialValue={initialValue}>
      <PlateContent />
    </Plate>
  );
};