Rendering a React component inside a node

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?

@marijn: you mean retrieving the react component from that DOM subtree ? That is not needed. Only the DOM node to which the react component has been mounted to is needed for unmounting:

ReactDOM.unmountComponentAtNode(domContainerNode)

and domContainerNode elements can easily be found with a DOM query in the hook, from the root of the subtree to be deleted.

ProseMirror will ‘drop’ whole subtrees at a time, without recursing through them, so an unmount-related callback would not be called for every node, but only for each subtree that’s dropped. So you’d want to scan those in the callback, to find the relevant React components.

Does that sound like it’d work?

Yes, that is fine. In the callback, the React cleanup can be done, for each component within that dropped subtree.

Implemented in this commit, which will be part of the upcoming 0.11.0

1 Like

great, thanks, I look forward to 0.11.0

how could callback be implemented for cleaning in react?

Hey guys - I have been trying to render React components as NodeView too. I have created this repo to show what I am currently doing:

  1. Use a NodeView to render the component
  2. Instead of using ReactDOM.render, I am using ReactDOM.createPortal. I return the portal component to my editor component which then renders it on the page for it to access my app’s context
  3. I’ve wrapped the NodeView in a React context so that its props are available within the component using hooks

Is this how you guys are approaching it too?

4 Likes

Thank you for the idea. Actually I’m trying to replicate this, but now I think prosemirror is split into different modules. So now where can we find { Block } coz we can’t find Block in prosemirror/dist/model ?

There is no export named Block in the entire library. See the reference manual to look up specific exports.

Sorry If I’m wrong but I was referring to this prosemirror-with-react-rendered-node/example-node.js at master · ericandrewlewis/prosemirror-with-react-rendered-node · GitHub