Search code examples
typescriptvuejs3typeerrortiptap

How to v-bind in multiple object array in vue3 with typescript?


I was made texteditor in vue3 + vite + ts, with tiptap, but in item.type , item.icon ... all item attributes say 'error'.

That say ToolbarItem | Divider not have each other attributes:

item.type => Property 'type' does not exist on type 'ToolbarItem | Divider', Property 'type' does not exist on type 'ToolbarItem',

My code in parents component:

<template v-for="(item, index) in items" :key="index">
    <div v-if="item.type === 'divider'" class="divider" :key="`divider${index}`" />
    <Child v-else :key="index"
            :icon="item.icon" :title="item.title"
            :action="item.action" :isActive="item.isActive"/>
</template>
interface ToolbarItem {
    icon: string,
    title: string,
    action: Function,
    isActive?: Function
}

interface Divider {
    type: string
}

const items: Array<ToolbarItem | Divider> = [
    {
        icon: 'bold',
        title: 'Bold',
        action: () => props.editor.commands.toggleBold(),
        isActive: () => props.editor.isActive('bold'),
    },
    {
        type: 'divider'
    },
    ...
]

My code in Child component:

interface ToolbarButtonProps {
    icon: string,
    title: string,
    action: Function,
    isActive?: Function
}

const props = defineProps<ToolbarButtonProps>()

I hope to separatly inject items to div or Child and stop see error message


Solution

  • that say ToolbarItem | Divider not have each other attributes

    Like the error suggests, your array of objects do not have similar properties. JavaScript allows you to access properties that do not exist in an object. For ex:

    const fruit = { shape: 'round', color: 'red' };
    console.log(fruit.size); // undefined
    

    Now, while JavaScript allows this, we shouldn't just use it. It becomes harder to read the code since you'd expect that fruit.size returns the value of size, but it results to undefined instead. This is why TypeScript is complaining.

    There are a couple of ways to address the TypeScript issue, however, perhaps we can try re-thinking how we create our template.

    <template v-for="(item, index) in items" :key="index">
        <div v-if="item.type === 'divider'" class="divider" :key="`divider${index}`" />
        <Child v-else :key="index"
                :icon="item.icon" :title="item.title"
                :action="item.action" :isActive="item.isActive"/>
    </template>
    

    Looking at this code, it seems the purpose of the div element is to add some sort of divider or margin between each rendered Child component. Try reworking it this way:

    <template>
      <section v-for="(item, index) in items" :key="index">
        <Child
          :key="index"
          :action="item.action"
          :icon="item.icon"
          :isActive="item.isActive"
          :title="item.title"
        />
        <div
          v-if="index < items.length"
          :key="`divider${index}`"
          class="divider"
        />
      </section>
    </template>
    

    With this, you don't need a "divider" in items and you don't need to conditionally render Child:

    const items: Array<ToolbarItem> = [
      {
        icon: 'bold',
        title: 'Bold',
        action: () => props.editor.commands.toggleBold(),
        isActive: () => props.editor.isActive('bold'),
      },
      ...
    ]
    

    Here is an example.