tiptap: Problem with Multiple Editors and v-for when deleting/splice an element in the array

What’s the bug you are facing?

We know its always hard to understand others projects setup as maintainer, so i try to explain this quickly:

We use tiptap as a very versatile place to map data and mixed content, like a very smart input text field 😃

image

Now we have a very strange behavior of that “multiple tiptap editor” setup (they do not sharing anything to each other), when we remove an element in that array (this is what the iteration looks like and integration-map-field holds the tiptap editor instance):

<template v-for="(field,index) in item.config_json.fields">
    <integration-map-field
      :key="index"
      v-model="item.config_json.fieldMap[field.identifier]"
      :editable="true"
      :label="field.label"
      :type="field.type"
      :required="field.required"
      :fields-collection="item.pool.typeEntity.fieldsCollection"
      @remove="removeField(item, index, field.identifier)"
    />
  </template>

then it removes the element, but at the absolute wrong position, its no matter what, always removes the latest tiptap editor. For example we remove this one (array.splice(…)):

image

It removes the wrong editor, it always removes the last tiptap editor, but as you can see the array iteration has been correctly adjusted, the field title has been removed from the array:

image

This is so strange, i could not find anything regarding this. So i was looking for “uniqueids” or whatever. Maybe there is global instanced variable i need to consider?

  • i played around with this.editor.destroy() without any luck
  • i tested with vue’s $forceUpdate()
  • i tested without tiptap (just added a textarea) and everything works expected.
  • my environment is nuxt 2 with vuetify

Which browser was this experienced in? Are any special extensions installed?

Chrome, Firefox. It happens on all browsers

How can we reproduce the bug on our side?

Take a look here: https://codesandbox.io/s/tiptap-vue-multiple-editor-issue-oxr1lo?file=/src/App.vue

Can you provide a CodeSandbox?

Update: Yes, its easy to reproduce, just add 3 or more items and remove an item in the middle: https://codesandbox.io/s/tiptap-vue-multiple-editor-issue-oxr1lo?file=/src/App.vue

What did you expect to happen?

It should removed the editor in the row, not the last one. it always removes the last one no matter what we do 😃

Anything to add? (optional)

This is what the component looks like … i have removed unnecessary logic and markup for better readability:

The Component
<template>
  <div>
      <editor-content
        :editor="editor"
      />
      <v-btn
          icon
          @click="removeItem"
        >
          <v-icon small>icon icon-remove</v-icon>
      </v-btn>
  </div>
</template>

<script>
// https://stackoverflow.com/questions/48145727/insert-character-at-cursor-position-in-vue-js
import IntegrationMapFieldMapperInputSpan from './IntegrationMapFieldMapperInputSpan.vue'
import { Editor, EditorContent, Node, VueNodeViewRenderer, mergeAttributes} from '@tiptap/vue-2'
import Document from '@tiptap/extension-document'
import Text from '@tiptap/extension-text'

const Div = Node.create({
  name: 'div',
  group: 'block',
  content: 'inline*',
  parseHTML() {
    return [
      { tag: 'div'},
    ]
  },
  renderHTML({ HTMLAttributes }) {
    return ['div', mergeAttributes(HTMLAttributes, {style:"vertical-align: text-top; line-height:30px"}), 0]
  },
})

const Variable = Node.create({
  name: 'variable',
  group: 'inline',
  inline: true,
  atom: true,
  selectable: false,
   addAttributes() {
   // removed the attributes
  },
  parseHTML() {
    return [
      { tag: 'span' },
    ]
  },
  renderText({ node }) {
    return `${node.attrs.label}`
  },
  renderHTML({ node, HTMLAttributes }) {
    return ['span', mergeAttributes(HTMLAttributes), node.attrs.label]
  },
  addNodeView() {
    return VueNodeViewRenderer(IntegrationMapFieldMapperInputSpan)
  }
})

export default {
  components: { EditorContent },
  props: {
    // removed the props
  },
  data:() => ({
    editor: null
  }),
  computed: {
    parsedValue: function() {
     // we do more things here, fulfill the array
      return {
        "type": "doc",
        "content": contentArrays
      }
    }
  },
  mounted() {
    this.editor = new Editor({
      extensions: [
        Document,
        Div,
        Text,
        Variable
      ],
      autofocus: 'end',
      content: this.parsedValue,
      onUpdate: () => {
        const values = []
        const content = this.editor.getJSON().content
        // we do more things here
        this.$emit('input', values)
      },
    })
  },
  beforeDestroy() {
    this.editor.destroy()
  },
  methods: {
    removeItem() {
      this.$emit('removeItem')
    }
  }
}
</script>

Did you update your dependencies?

  • Yes, I’ve updated my dependencies to use the latest version of all packages.

Are you sponsoring us?

  • Yes, I’m a sponsor. 💖

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 17 (6 by maintainers)

Most upvoted comments

Hey @nadar.

I’m going to take a look into this issue to find out what exactly is happening here. Thanks for reporting!