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 or useEditorState hooks.

Internally, useEditorState is a wrapper for useEditorRef. The only difference is that useEditorState causes React to re-render whenever the editor state changes, whereas useEditorRef does not cause a re-render. Since editor is mutable and is updated by reference, useEditorRef will be sufficient (and more efficient) in most situations.

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

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 of Plate

Wrap PlateContent and the sibling in Plate, and then use useEditorRef or useEditorState from within the sibling.

const Toolbar = () => {
  const editor = useEditorState();
  // Do something with editor
  // ...
};
 
const Editor = () => (
  <Plate>
    <Toolbar />
    <PlateContent />
  </Plate>
);

From an Ancestor

If you need to access the editor instance from an ancestor of PlateContent, wrapping the relevant components in a Plate is the preferred solution. If this is not an option, you can instead use the editorRef prop to pass a reference to the editor instance up the React component tree to where it is needed.

The editorRef prop can be used with useRef, useState, or a custom ref callback. Regardless of which you use, you'll need to handle the case where editor is null. This happens when the editor hasn't had a chance to render yet or has unmounted.

With a Ref Object

const App = () => {
  const editorRef = useRef<PlateEditor | null>(null);
 
  const handleSomeEvent = useCallback(() => {
    // editor has type PlateEditor | null
    const editor = editorRef.current;
 
    if (editor) {
      // Do something with editor
    }
  }, []);
 
  // Pass editorRef and handleSomeEvent down to where they're needed
  // ...
};
 
const Editor = ({
  editorRef,
}: {
  editorRef: MutableRefObject<PlateEditor | null>;
}) => (
  <Plate editorRef={editorRef}>
    <PlateContent />
  </Plate>
);

With State

If you want your ancestor component to re-render when the editor content changes, you may want to use useState to store your editor instance. Since the editorRef callback is only called once when the editor first mounts, you'll also need to manually trigger a re-render by updating a counter whenever the onChange handler of Plate is called.

Using editorRef with useState without a counter is equivalent to using useEditorRef instead of useEditorState (the difference is discussed above). Most of the time, if you don't need the ancestor component to re-render on every change, you should be using useRef instead.

const App = () => {
  const [editor, setEditor] = useState<PlateEditor | null>(null);
  const [, handleUpdateEditor] = useReducer((x) => x + 1, 0);
 
  // Pass editor, setEditor and handleUpdateEditor down to where they're needed
  // ...
};
 
const EditorPreview = ({ editor }: { editor: PlateEditor | null }) => {
  // html has type string | null
  const html = useMemo(
    () =>
      editor &&
      serializeHtml(editor, {
        nodes: editor.children,
      }),
    [editor, editor?.children]
  );
 
  if (!html) return null;
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
};
 
const Editor = ({
  setEditor,
  handleUpdateEditor,
}: {
  setEditor: (editor: PlateEditor | null) => void;
  handleUpdateEditor: () => void;
}) => (
  <Plate editorRef={setEditor} onChange={handleUpdateEditor}>
    <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>
  );
};