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 usePlateEditorRef or usePlateEditorState hooks.

Internally, usePlateEditorState is a wrapper for usePlateEditorRef. The only difference is that usePlateEditorState causes React to re-render whenever the editor state changes, whereas usePlateEditorRef does not cause a re-render. Since editor is mutable and is updated by reference, usePlateEditorRef 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 (or PlateProvider) component, including Plugin Components.

const ParagraphElement = ({
  className,
  children,
  ...props
}: PlateElementProps) => {
  const editor = usePlateEditorRef();
  
  const handleClick = useCallback(() => {
    console.log('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 = usePlateEditorRef();
  
  useEffect(() => {
    const interval = setInterval(() => {
      console.log('The editor is ', editor);
    }, 1000);
    
    return () => clearInterval(interval);
  }, [editor]);
  
  return null;
};
 
export default () => (
  <Plate>
    <CustomEffect />
  </Plate>
);

From a Sibling of Plate

Wrap Plate and the sibling in PlateProvider, and then use usePlateEditorRef or usePlateEditorState from within the sibling.

Note that certain Plate props, such as plugins and initialValue, must be lifted up to the PlateProvider. See From an Ancestor if this is impractical or impossible for you to achieve.

const Toolbar = () => {
  const editor = usePlateEditorState();
  // Do something with editor
  // ...
};
 
export default () => (
  <PlateProvider>
    <Toolbar />
    <Plate />
  </PlateProvider>
);

From an Ancestor

If you need to access the editor instance from an ancestor of Plate, wrapping the relevant components in a PlateProvider 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 MyEditor = ({
  editorRef,
}: {
  editorRef: MutableRefObject<PlateEditor | null>;
}) => <Plate editorRef={editorRef} />;

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 usePlateEditorRef instead of usePlateEdtiorState (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 MyEditor = ({
  setEditor,
  handleUpdateEditor,
}: {
  setEditor: (editor: PlateEditor | null) => void;
  handleUpdateEditor: () => void;
}) => (
  <Plate
    editorRef={setEditor}
    onChange={handleUpdateEditor}
  />
);

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([
  // ...
]);
 
export default ({ 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(editor, {
      element: initialHtml,
    });
  }, []);
  
  return (
    <Plate
      plugins={plugins}
      initialValue={initialValue}
    />
  );
};