How to update nodeviews without reusing existing nodes

In my app, I have a NodeSpec with

attrs: {
                id: {default: 'id'},
                lang: {default: ''},

and a Nodeview

class View {
  constructor(node, view, getPos) {
    const dom = document.createElement('div')
    const label = document.createElement('label')
    label.innerText = mytext(node, myConfig)
    this.contentDOM = document.createElement('div')
    this.dom = dom

Here mytext(node, myConfig) will get some text by the node’s id and lang from myConfig.

Everything works as expected at first.

Now myConfig updates (from network). Accordingly, the document is also modified by some transactions. Some nodes are deleted, some added. Both cases work fine (everything updates accordingly).

But for some nodes, the id and lang does not change. The label in the nodeview does not update as the result of mytext(node, myConfig) (since myConfig is updated).

I find in NodeViewDesc.prototype.updateChildren, it tries to reusing existing nodes, which might be the cause. I really do not want to introduce an extra attribute for the node, so I am wondering if it is possible to toggle off this reusing mechanism.

The view isn’t aware of the dependency of your node views on myConfig, so indeed, it won’t automatically redraw these. And of course it reuses existing nodes—otherwise it’d redraw the entire document on every change, which would be terrible. You’ll have to somehow arrange for the node views to be redrawn—one possibility would be to, when the metadata changes, add a node decoration to all nodes of this type, since changes in decoration will cause the node to be updated.

Ok, I will try it out. Thanks.

Howdy, I have a similar “issue” where the NodeView should redraw when the value of editable changes. It seems like something of a hack to wrap the views in decorations, any thoughts on how to achieve that?

There’s currently no other way—the editor treats node views as only depending on their node and decorations, so other changes don’t trigger updates.

It might be reasonable to add a way to explicitly iterate over the active node views, but that doesn’t exist yet and I don’t know when I’d get to working on that.

Gotcha, I understand you have to limit it somewhere – the editor changing between read-only and editable seems significant enough that it might be good to make NodeViews also dependent on that value, but not If I’m the only one that’s asked for it :slight_smile:

@marijn Is it expected that a node view is reused even when the node view of the same type is pasted and it has a different value in a key in its node.attrs?

More specifically, when I paste a node view to the left of an existing node view of the same type, and when these node views share the same parent node, it seems that updater.addNode(child, outerDeco, innerDeco, view, off) is called with a child that is a different node view than what I am pasting.

I understand that the node view is built to reuse an existing node whenever possible, but in my case the two node views have different values in a key in their node.attrs. I even tried adding decorations to these node views when a paste meta transaction is detected in a plugin, but it did not do anything meaningful since the value in a key in node.attrs of the node view does not change.

What I expected was that when I paste a node view that has a different value in a key in its node.attrs compared to the node view that is about to be reused, ProseMirror should not reuse the node view since their identities are different. Do you have any recommendations that would help me to fix this issue?

Thanks a lot for building this wonderful Editor!

Yes. Your update method is responsible for checking whether reuse is possible, and returning false if not.

@marijn Thanks for the prompt response. I have one more question. If I want to return false in my update method in the node view for when the node view is pasted or deleted, what is the best way to handle this? Do I detect a meta transactions for paste or delete in a plugin and add a decoration to the node view?

That’s not something that should be handled on the level of the node view—it should just be a shallow representation of a given node structure, not tied to the identity of any specific node. If you do need to associate additional state with your view, you should manage it in a plugin and use node decorations to pass information through to the node view.

1 Like

Doing this is not going to be useful for collaborators looking at the same editor, right? My node view is to be persisted in the database, and I also want it to be copy pastable outside of the editor. That’s the reason why I keep a state in a node.attrs, but it looks like this state is not reflected very well when ProseMirror tries to reuse the node view. It sounds like I just need to return false in the update method whenever I want the node view to be recreated but that might not play well when decorations are being added… My use case for the node view requires a state provided by a decoration as well as a state provided by node.attrs.

Any state that’s part of the actual node (such as in attrs) should be trivial to sync with the node view, even across clients.

1 Like

For posterity, I’m sharing my learning here.

NodeView.update can receive a node that may not be identical to the originally constructed node. You are responsible to check the equality of the node given to the update method and the original node in the constructor to decide whether to let ProseMirror to reuse an existing node or not.

When the update method returns true, ProseMirror wants to reuse sibling node views of the same type whenever possible. By default, it looks only at the type of the node to determine if reuse is possible. If your node view contains a node attribute that uniquely identifies the node, it must be respected. It is not enough to only look at the node type. So if the node attribute that you use to uniquely identifying the node view is different, you must return false in the update method to ensure ProseMirror does not try to reuse a sibling node view and create a fresh node instead.

1 Like