Widget made with react is being redrawn every time

Hi there. I have some problem with decoration widgets - in my case, it's being redrawn every time a doc is updated, and it affects the performance of the page. So, the contract becomes laggy even when typing, because of the redrawing. This is how I create controls with widgets via decorations:

constructor() {
    super({
        state: {
            init: (config, editorState) => {
                return {
                    decorations: this.mapControlsToDoc(editorState.doc),
                }
            },
            apply: (tr, oldState) => {
                return tr.docChanged ? { decorations: this.mapControlsToDoc(tr.doc) } : oldState; },
            },
            props: {
                decorations: editorState => this.getState(editorState).decorations,
            },
    });
}

mapControlsToDoc = (doc) => {
    let decos = [];

    doc.descendants((node, pos) => {
        if (node.type === schema.nodes.target_node) {
            let widget = this.createControlsWidget();
            decos.push(Decoration.widget(pos + 1, widget));
        }
    });

    return DecorationSet.create(doc, decos)
};

createControlsWidget = (pos, actionBlockType) => {
    const widget = document.createElement('div');

    widget.className = 'controls';
    ReactDOM.render(
        <ControlsUIReactComponent
            onDelete={() => this.deleteBlock()}
        />
        widget
    );

    return widget;
};

Widgets are recreated every time the contract is updated. There is a couple of problems because of this:

  • A contract is really slow in this case, even when typing. So, I assume, in this case, it actually shouldn’t be redrawn? I tried to use the key property but no luck.
  • Another problem is that I need to know when the decoration is destroyed so I can unmount react component, but I didn’t find any event or anything else I can use.

Thanks.

You didn’t show your node view code. Does it define an update method?

My node view is quite simple and doesn’t define an update method. Should I somehow check in update method if widgets are same or there is a better way?

Regarding destroying decorations - in update method there is decos param, should I check it for emptiness and then destroy my react components?

That might be the problem, if the node has content this’ll cause the view to redraw it every time. If that’s the case, define an update method that just ignores its second parameter and checks whether the first is compatible with its original node (using sameMarkup).

Not sure if this is relevant but I had a similar issue and found that the following prevented unnecessary redraw:

Decoration.widget(..., {key: pos})

Where pos is the argument from the descendants callback.

I got it! But it turned out in my case the problem is that every time doc is changed, I creates new widget (and React component is being rendered inside createControlsWidget function):

doc.descendants((node, pos) => {
    if (node.type === schema.nodes.target_node) {
        let widget = this.createControlsWidget();
        decos.push(Decoration.widget(pos + 1, widget));
    }
});

So, if I got it right, new decoration always creates widget and then compares it with existing one? This is what I’ve seen in Linter example. Is there a way to check if node already content some decoration with target widget, and if it does, do nothing. In my case widget are never changed, so I’d like only recalculate those positions and never change a widget. Is it possible?

Ah, sorry, my previous response was completely irrelevant (I thought you were creating node views, but these are widget decorations).

So yes, if you directly call createControlsWidget and that function creates a React root, that will happen a lot, obviously. But note that you can also pass a function as second argument to Decoration.widget. If you do that and provide a key that identifies the type of widget, the view will be able to avoid redrawing the widget most of the time, and will only call the DOM creation function when actually necessary.

1 Like