Hi, I’m working on an editor that replaces certain AST nodes with interactive components when possible. Similar to the true/false checkbox example, but extended to things like augmenting [r, g, b] or [h, s, l] with a RGB or HSL color picker.
In my app, an React component renders the Codemirror editor and maintains a ref to its view. Is it possible to somehow return a React component in the toDOM method of a Decoration, or replace toDOM with something else capable of rendering a React component in the same tree as the Editor component?
I would like to render Decorations using React because it most closely matches how I build UI throughout the rest of my app, and would allow me to use the same components (e.g. color picker) in the editor and in an exported “view mode”. Otherwise, I would need to rewrite the rendering code for things like the color picker to be vanilla, which I would prefer not to do.
I generally recommend against this, since it introduces a bunch of extra indirection an inefficiency that, for all but the most complicated widgets, isn’t going to pay off, but I believe react has a feature called portals that may help with this—have the toDOM method return a parent node, and then asynchronously wire it up to a react portal.
Using the element returned from toDOM as a portal container did not work for me. Anything that I attach to the container element from my React code gets deleted.
Using React Portal works pretty well for this.For the widget, just create an empty DOM element. Then pass a reference of this back to your React code (I instantiate the plugin from React and have the plugin return the widget instance).
class PortalWidget extends WidgetType {
container: HTMLElement;
constructor() {
super();
const container = document.createElement("span");
container.className = "relative";
this.container = container;
}
toDOM() {
return this.container;
}
ignoreEvent() {
return false;
}
}
function createMyPlugin() {
const widget = new PortalWidget();
return {
widget,
// anything else you want your plugin to do, for example a decoration that adds the widget to the editor
};
}
And then using Radix UI’s Portal:
import * as Portal from "@radix-ui/react-portal";
const myPlugin = createMyPlugin();
...
<Portal.Root container={myPlugin.widget.container}>
hello from React
</Portal.Root>
Thanks for sharing that. I am running into a similar challenge where I would like to create a widget that calls React.createPortal(<Component />, containerElement) in the toDOM() function and returns the containerElement back to CodeMirror. My Component in that case does not render.
Would you be able to possibly share a small working example of the solution you described? Thanks!