How can i insert a react component as a node

I am using prosemirror as my text editor and I need to click on a button to create a node with react and have props as an id

1 Like

It will probably be tricky due to Reacts virtual DOM.

Have you looked into Portals yet? Probably still tricky to reconcile both worlds, but might be doable. Otherwise probably ReactDOM.render(element, container) but that would create individual React instances which live outside you remaining app if I’m not mistaken.

1 Like

We have a system we built to render NodeViews as React components, so it’s definitely doable. It has worked quite well for us so far.

Atlassian has a ProseMirror-based editor that is open source and they also have a similar concept. Take a look there and hopefully that should give you some good ideas: ReactNodeView class in Atlaskit editor

2 Likes

Try use nodeview or view, they both have the update method, where you can use redux to dispatch an action, the payload is the component you want.

Check out this full editor example by TeemuKoivisto. His implementation is inspired by Atlassian’s editor.

We’ve actually written something similar, but we’re having trouble with giving the react components rendered from within each node view access to the context being provided from the app root. Looking at react dev tools, it looks like it’s because each time we call ReactDOM.render, react creates a separate tree. Looking at the example linked above, I’d imagine it has the same issue.

Also, its use of ReactDOM.createPortal doesn’t seem to work, both based on the comment and my own testing. ReactDOM.createPortal returns a renderable, so it still needs to be rendered into the react tree, which I don’t think is happening.

@jhnsnc I’m interested in how you guys are doing it. I did look through the ReactNodeView class you mentioned, and I see that you guys are using unstable_renderSubtreeIntoContainer, which seems to allow for a context to be provided. If I’m not mistaken, that function has been deprecated, so I’m wondering how you guys eventually plan to switch over to ReactDOM.createPortal. I couldn’t find a good way to provide context to a portal aside from rendering it as a child of provider.

Yeah you’re right. I’ve given up on this for now. Just gonna use good ol javascript. I’ll try out remirror’s approach once I get time.

@jschen 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

Was this how you guys are approaching it too?

1 Like

Hey @johnkueh! I haven’t gotten a chance to test out your code yet. However, just reading through it, yes, it is conceptually very similar to how we implemented our ReactNodeViews. Unless I am misunderstanding something, I think it still has the same problem that we haven’t figured out how to solve, which is to be able to provide a context at the editor component level to all of the node views. Essentially, say you were to use something like redux, and you wrap the entire editor component with the redux store provider (you’d add something like <Provider store={appStore}></Provider> around your edtior component), I don’t believe each of the react node views will have access to the global redux state via the context, since they aren’t rendered as part of the same react tree.

Hi @jschen,

I can access the app context within the react component within the node views. This can only happen because instead of ‘rendering’ the react component in the nodeview, its creating portals from them. The portals are then passed back to the editor component and renders it within the app context (same react tree). See here: https://github.com/johnkueh/prosemirror-react-nodeviews/blob/master/src/components/Prosemirror.tsx

Does that work?

Okay, just dug through your code some more. I think I understand how your code works now, but just to clarify, the react tree you end up with is something like the following:

<ReduxProvider>
    <ReactNodeViewContext.Provider>
        <div className="editor">
            <div className="node-view-container-a" />
            <div className="node-view-container-b" />
        </div>
        <NodeViewA />
        <NodeViewB />
    <ReactNodeViewContext.Provider>
<ReduxProvider />

where NodeViews A and B are portaled to their corresponding node view dom elements, correct? We did consider doing it this way, but the NodeViews are rendered as siblings of the editor. Ideally, the NodeViews should be descendants of the editor, since that’s more logically consistent with what a node view represents within the editor, at least in my opinion. For example, the structure we really want is something like:

<ReduxProvider>
    <div className="editor">
        <div class="node-view-container-a">
            <NodeViewA />
        </div>
        <div class="node-view-container-b">
            <NodeViewB />
        </div>
    <div/>
<ReduxProvider />

This might just be impossible given how React is implemented though. I’m definitely not super familiar with the inner workings of React :slightly_smiling_face:

@johnkueh Just to follow up, we eventually decided to go the route of using portals as well. In our implementation, we had to re-register the portals in the NodeView’s update function in order for the portals to re-render given a change to the NodeView’s underlying node’s attributes. Anyway, just thought I’d circle back and let you know. Thanks for sharing your library - it was very helpful!

@johnkueh, @jschen - Is using portals like that actually performant?

We haven’t actually rigorously tested our implementation on a long doc with a ton of node views, but just thinking through it, it’s performant insofar as prosemirror doesn’t unexpectedly destroy/recreate your NodeViews. We did have to pass in a custom ignoreMutations function to prevent this, but otherwise the rendering logic is akin to react rendering children into a react tree.

Thanks. And the fact that the DOM looks how you described with portals doesn’t concern you? I mean at that point, what’s the reason behind not doing the NodeViews it in plain JS? Is it because they need to interact with the rest of the app which is React (Context and stuff like that)?

Yep, I’d say being able to share contexts with the rest of the application is the main benefit to having the node view components be rendered within the same react tree.

1 Like

Any chance you have an example of how you implemented this? This seems like the way we want to go also. Great work getting to this solution!

1 Like

Unfortunately I can’t share our version of the code, but I’d follow pretty much what @johnkueh has implemented and then make any necessary tweaks. The main addition we made in order for the component to be properly updated when the node view’s underlying node updates was a way to update the node view context from the NodeView’s update method.

Edit: Just realized I mentioned recreating the portal in the update method in my old comment. That actually isn’t necessary. Like I mentioned above, you just need some way to update the node view context that your providing to the node view component. I’d recommend looking into something like storing the node view context in a useState hook and then calling the state update function from the NodeView update function.

1 Like