Unmounting a Decoration Widget created with React

I am using the (view, getPos) => … form of the toDom param in Decoration.Widget(). In that function, I am using ReactDOM.Render(). It all works great, except it doesn’t clean up when destroyed. What would be the proper way to detect widget destruction in such a case in order to call react-dom’s unmountComponentAtNode() to clean up?

For now, I am going to see about solving this feature with a NodeView which gives a bit more fidelity to the DOM lifecycle than does a widget.

1 Like

I guess adding support for a destroy methods to widgets should be possible (it’ll work much like the node view destroy currently works). If you want to submit a patch that adds that, I’ll gladly take a look.

Sure, I can take a crack at that.

I had the same problem and I solved it by keeping the container of the react component in the plugin state and calling ReactDOM.unmountComponentAtNode(container) every time the decoration is created and re-rendering the widget again in the same container.

// This function returns a function that you can pass to the Decorations.widget 
// while memorising the container, so you don't need access to the whole state
const renderWidget = (container) => (view, getPos) => {
  ReactDOM.unmountComponentAtNode(container)
  ReactDOM.render(<MyComponent />, container)
  return container
}

const createDecorations = (container, pos) => {
  return [Decoration.widget(pos, renderWidget(container)]
}

new Plugin({
  state: {
    init(config) {
      return {
        decorations: DecorationSet.create(config.doc, []),
        widgetContainer: document.createElement('div')
      }
    }
    apply(tr, state) {
      // If you need to map the decorations
      if (...) {
        return {
          widgetContainer: state.widgetContainer,
          decorations: state.decorations.map(tr.mapping, tr.doc, {
            onRemove() {
              ReactDOM.unmountComponentAtNode(state.widgetContainer)
            },
          }),
        }
      }
      // Apply logic here and calculate the pos if needed
      return {
        widgetContainer: state.widgetContainer,
        decorations: createDecorations(state.widgetContainer, pos)
      }
    }
  }
})
1 Like

@AwnryAbe any luck with the patch?

Not sure how complex it would be but I’d be happy to take a crack if pointed in the right direction!

@marijn If I wanted to implement this, would it be as simple as:

I’d be happy to help take a stab at this but the internals of NodeView/Decoration are very new to me.

Thanks!

I don’t think you need to involve the Decoration class directly in this—just WidgetType needs to be able to store a destroy function (from the spec passed to Decoration.widget), and WidetViewDesc needs to call through to that in its destroy method.

1 Like

Ah OK, I see that now. I was overthinking it!

Just created a PR for this: feat (Widget): Add a destroy function to Decoration.widget by jamesopti · Pull Request #111 · ProseMirror/prosemirror-view · GitHub

2 Likes

Thank you! I got pulled in a completely different direction for a year or two and never got back to this.