Rendering a React component inside a node

I created an example to show how to create a custom node type that renders as a React component inside the editor.

1 Like

Nice. Are there any things in ProseMirror’s API that make it difficult to use in a React setting? I’m thinking of separating out the view/DOM logic, and making it possible for client code to control the update cycle, but I don’t really have enough experience with React to know whether this would really help.

Yep! Like what you’re thinking in #378 and added a comment there.

Nice example - I’m also interested in this use case.

We’ve gotten some experience with react + prosemirror already. A slight pain point for us has been that you have to decide how the custom node is rendered at the time you define the schema.

We wrap the ProseMirror instance itself inside a ProseMirrorReact-component. And a common scenario is that the ProseMirrorReact-component takes props that configures certain node-rendering related aspects (e.g. render all nodes green instead of orange).

In that case you’d have to defer schema creation until componentDidMount, which is a little cumbersome and feels like a mix of data model and view concerns. Ideally I’d like to be able to create my schema once, then decide on how to render the nodes later on.

Maybe this would be covered by “separating out the view/DOM logic” that you mention above, @marijn ?

When would the color of these nodes in your example change? A node’s representation being fully determined by its type and attributes is a rather nice property, since if we don’t have that, the update algorithm can’t really know what it should be redrawing when.

They would typically be rendered differently across different instances of ProseMirror.

How much of this could you solve by adding different CSS classes to your instances and changing looks based on those?

Something else that I’m struggling with in our React setting is the updating of ProseMirror options and plugin options. I have a React component which renders a ProseMirror instance and the component’s props contain options, which should be passed to the ProseMirror instance (including plugin options). When the props of the component change I would like to update the ProseMirror instance along with the plugin options.

Before the 0.8 release I handled this case using the setOption method of ProseMirror to update the options, but I’m still in the process of migrating to 0.8.3 and do not have a solution for this yet (as setOption has been removed and the handling of plugins has changed).

The remaining options are now immutable (by design). Plugins can be disabled and re-enabled with their detach and attach methods.

Thanks, I was able to migrate this part of the code by using the detach and attach methods.

I guess a simple case like color could be solved using CSS. But if/when you want the nodes to produce different dom-trees for different options / configurations, you’d be stuck (lets say you want to render some nodes with a more detailed view, then feature togglig using CSS quickly becomes messy).

Have you made any React node examples with eg <input> elements? Last time I tried this, PM was catching the input events.

That bug has been fixed a while ago, shouldn’t be the case in 0.7.0 and 0.8.0 anymore.

1 Like

Thanks @ericandrewlewis for creating this example. Very helpful!

One issue I’m hitting is that the React component doesn’t unmount when it is removed.

React components unmount when they are destroyed, meaning that their listeners, helper functions, etc are cleaned up. However, deleting the embedded react element from within ProseMirror does not unmount the component.

See this fork here: GitHub - isTravis/prosemirror-with-react-rendered-node

You can insert many react components, delete them all, and if you look at the console, you’ll see they’re all still in memory ticking away.

For the documents I’m trying to support, this would turn into a costly memory leak quickly.

React does provide a manual way of unmounting nodes, but the trick is having the right hook to call that.

Any advice?

Do I understand correctly that React event handlers are, in a way, global, and have to be unregistered even if the DOM nodes they apply to are removed from the document, or they will leak? And what do you mean by helper functions and ‘ticking away’?

Correct.

Apologies - it was at the end of a long day - I should have worded that more clearly. In the demo repo I included, I added a setInterval with a console.log to demonstrate that the component was still existing in memory even though the element was removed from the DOM. By ‘ticking away’, I meant that the setInterval was still firing. By helper functions, I mean that any listeners created for that React component remain in memory, even though the element was removed from the DOM.

Here is a GIF showing the React dev tools in Chrome. You can see that each component remains in memory even after I delete them from the DOM.

React Components Remain

That does look like a problematic mismatch between React code and ProseMirror’s update algorithm then – ProseMirror makes the assumption that DOM nodes that aren’t needed anymore can simply be removed from the DOM and forgotten about. It doesn’t unmount them, in fact, it doesn’t even individually go over them — it just removes DOM nodes that aren’t used in the current render, which may contain a deep tree of document nodes, without scanning them – but even if it did scan them, it’d be very awkward to reconstruct the document nodes from the DOM nodes, so being able to run code when a rendered node is removed is probably not going to be supported.

Ya - that makes sense.

This page describes exactly our issue: https://facebook.github.io/react/blog/2015/10/01/react-render-and-top-level-api.html

The official React recommendation is to create a library that manages all of the React nodes, rather than individually calling React.render() every single time we want to add a node.

I’ll keep playing a bit and see if I can come up with anything given the existing ProseMirror API. Worse comes to worse, I could have the React components manage their own removal - for example, check every 5 seconds to see if their randomly assigned ID is still in the DOM, and if not, call unmount(). Not the prettiest solution, but it would at least prevent any memory leaks from getting too large.

Edit: I think I’m wrong about the italicized above. I can’t seem to find any way to unmount a React component that is no longer in the DOM. The React way seems to deem that removal from the DOM should only happen through React unmounting, and there is no way to clean up otherwise. Will keep looking, but the above fallback doesn’t seem like an option.

Has anybody come up with a way to unmount Prosemirror embedded Rect components (ReactDOM.unmountComponentAtNode), when they get removed from DOM by Prosemirror ?

@marijn Would it be possible that PM provides a hook before removing dom nodes ? Then one could check whether there are React components within that part of the DOM tree and unmount them

@rsaccon Yes, that would be possible. I could add a callback that gets called whenever a subtree gets dropped. Is going from the React components from a DOM tree easy/fast?