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 toeditorthat almost never gets replaced. This should be the default choice.- Since
editoris a mutable object that gets updated by reference,useEditorRefis always sufficient for accessing theeditorinside callbacks. useEditorRefwill almost never cause your component to re-render, so it's the best choice for performance.
- Since
useEditorSelector- Subscribe to a specific selector based oneditor. 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
useEditorSelectorto access the relevant property. - The selector function is called every time the
editorchanges (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
equalityFnin the options touseEditorSelectorfor cases where===isn't sufficient.
- For this to work properly, you should make sure that the return value can be compared using
- If the selector function depends on any locally scoped variables, you should include these in the dependency list.
- Example usage:
useEditorState- Re-render every time theeditorchanges.- Using
useEditorStatewill 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.
- Using
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>
);If editor is modified by reference, why include it in dependency
lists?
Good question! Even though editor is usually modified by reference,
there are some situations in which it's replaced with a fresh instance, such
as when the editor is reset.
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
Platecomponent is mounted inside thePlateController - When all mounted
Platecomponents are marked as non-primary - Temporarily while
PlateContentis 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>
);
};