Vue: template can't be keyed, but can't replace template with div - need the v-for without a wrapper div element, nested v-for loops

Viewed 13251

I have this JSON:

{
   "data":{
      "1":{
         "color":"red",
         "size":"big"
      },
      "2":{
         "color":"red",
         "size":"big"
      },
      "3":{
         "color":"red",
         "size":"big"
      },
      "4":{
         "color":"red",
         "size":"big"
      },
      "5":{
         "color":"red",
         "size":"big"
      }
   }
}

that I display with this vue code:

<template>
...

<template v-for="(obj, pos) in this.breakdown" :key="pos">
    <table class="table-auto" >
        <thead>
            <tr>
                <th class="px-4 py-2">Property</th>
                <th class="px-4 py-2">Value</th>
            </tr>
        </thead>

        <tbody>
            <template v-for = "(obj2, pos2) in obj" :key="pos2">
                <tr>
                    <td class="border px-4 py-2">
                        {{pos2}}
                    </td>
                    <td class="border px-4 py-2">
                        {{obj2}}
                    </td>
                </tr>
            </template>
        </tbody>
    </table>
</template>
...
</template>

However I get the error '<template>' cannot be keyed. Place the key on real elements error. If I replace template with span or div, it works, but the styling is all out of place, so I need it to be without the wrapper element - I've read it's only achievable with the template tag, but then I'm not sure how to modify the v-for loops to remove the error.

4 Answers

Try to put v-for into template and key on tr.

<template v-for="(i, j) in list" >   
            <tr :key="'1tr-'+j"> 
               <td..../>
            </tr>
            <tr :key="'2tr-'+j"> 
               <td..../>
            </tr>
 </template>

This is working for me. Hope it will

In my case I need to render a sidebar menu. The menu items could be single elements or submenus. I tried this:

export interface DashboardRoute {
  icon: string
  label: string
  children?: string[]
}

const dashboardRoutes: DashboardRoute[] = [
  {
    label: 'Visão geral',
    icon: 'dashboard',
  },
  {
    label: 'Pessoas',
    icon: 'user',
  },
  {
    label: 'Categorias',
    icon: 'tags',
    children: [
      'Todas as categorias',
      'Alimentação',
      'Refeição',
    ],
  },
]

export default dashboardRoutes
<a-menu v-for="{ icon, label, children } in dashboardRoutes" :key="label" mode="inline">
  <a-sub-menu v-if="children">
    <span slot="title"
      ><a-icon :type="icon" /><span>{{ label }}</span></span
    >
    <a-menu-item v-for="child in children" :key="child">
      {{ child }}
    </a-menu-item>
  </a-sub-menu>
  <a-menu-item v-else>
    <a-icon :type="icon" />
    <span class="nav-text">{{ label }}</span>
  </a-menu-item>
</a-menu>

But this is not right. It will create multiple a-menu elements and should have a single one.

So I naively tried this:

<a-menu mode="inline">
  <template v-for="{ icon, label, children } in dashboardRoutes" :key="label">
    <a-sub-menu v-if="children">
      <span slot="title"
        ><a-icon :type="icon" /><span>{{ label }}</span></span
      >
      <a-menu-item v-for="child in children" :key="child">
        {{ child }}
      </a-menu-item>
    </a-sub-menu>
    <a-menu-item v-else>
      <a-icon :type="icon" />
      <span class="nav-text">{{ label }}</span>
    </a-menu-item>
  </template>
</a-menu>

which shows this error you found. So I googled it and found this issue. The solution is to move the key prop from template to the first child element, or to the siblings (if they exists - thats my case).

So, the solution will be

<a-menu mode="inline">
  <template v-for="{ icon, label, children } in dashboardRoutes">
    <a-sub-menu v-if="children" :key="label"> <!-- key comes here! -->
      <span slot="title"
        ><a-icon :type="icon" /><span>{{ label }}</span></span
      >
      <a-menu-item v-for="child in children" :key="child"> 
        {{ child }}
      </a-menu-item>
    </a-sub-menu>
    <a-menu-item v-else :key="label"> <!-- key comes here! -->
      <a-icon :type="icon" />
      <span class="nav-text">{{ label }}</span>
    </a-menu-item>
  </template>
</a-menu>

Vue does not support binding attributes on the template element. If u need :key and cannot use "div" you can use "span"instead.

    <span v-for="(obj, pos) in this.breakdown " :key="pos">
       <td >{{pos}}</td>
       <td>{{obj}}</td>
    </span>

Try to move v-for directly into table:

<table class="table-auto" v-for="(obj, pos) in this.breakdown" :key="pos">
Related