34

I have used vue.js for a couple of projects and I have been using the index as the key in the for loops

<div v-for="(item, index) in items" :key="index"></div>

...and have started to wonder if there are problems with that since examples usually use the ID of the item.

<div v-for="(item, index) in items" :key="item.ID"></div>
getsetbro
  • 1,798
  • 1
  • 22
  • 33
  • I basically have the same [question](https://stackoverflow.com/questions/76627314/using-a-proper-key-in-v-for-simply-circumvents-reactivity-and-breaks-performan) and the accepted answer has a bug that is "fixed" by using a key. – Simon Jul 07 '23 at 08:18

3 Answers3

44

Because arrays are mutable. The index of any given item can and will change if items are added to or removed from the array.

You want your key to be a unique value identifying only your unique component. A primary key that you create is always better than using an index.

Here is an example.

console.clear()

Vue.component("item", {
  props: ["value"],
  data() {
    return {
      internalValue: this.value
    }
  },
  template: `<li>Internal: {{internalValue}} Prop: {{value}}</li>`
})


new Vue({
  el: "#app",
  data: {
    items: [1, 2, 3, 4, 5]
  },
  methods: {
    addValue() {
      this.items.splice(this.items.length / 2, 0, this.items.length + 1)
    }
  }
})
<script src="https://unpkg.com/vue@2.2.6/dist/vue.js"></script>
<div id="app">
  {{items}}
  <ul>
    <item v-for="i in items" :value="i" :key="i"></item>
  </ul>
  <button @click="addValue">AddValue</button>
  <ul>
    <item v-for="(i, index) in items" :value="i" :key="index"></item>
  </ul>
</div>

Note that when addValue is clicked, the list on top represents the new numbers in the array where the truly are in the array; in the middle. In the second list below the button, the values do not represent the actual location in the array and the internal and property values do not agree.

Bert
  • 80,741
  • 17
  • 199
  • 164
  • @getsetbro There are ways to mitigate issues that come up when using an index as a key, and there may be cases were an index is your only choice, but the ideal case is to use a proper key. – Bert Jun 14 '17 at 14:20
  • Thanks for the example. What I learned from it is that the key property allows Vue to track properly where the item is in the array and render accordingly. However, I'm failing to understand why in the second list, internalValue would be different from value, when internalValue is simply assigned this.value. Is there anywhere I can read further regarding this subject? – AlexAlexson Nov 11 '18 at 08:08
  • 2
    @AlexAlexson the `data` function in a component only runs *once* in the lifetime of the component. That assignment is an initialization value. When the `value` property changes, there is nothing in the example component that updates `internalValue`. – Bert Nov 12 '18 at 14:22
  • @Bert, AlexAlexson, Follow the inline patch strategy of Vue, for the second list, since there is no correct index matching between application state and DOM, the new DOM element will be added in the end of list, and it will initialize internalValue = value (now it equals 5). So you will see it always has value of 5 for internalValue at the end of the list. For more information, you can refer to: https://deepsource.io/blog/key-attribute-vue-js/ – tuq Sep 25 '21 at 04:38
  • A quick win for unique key: `:key="something + '-' + '-' index + '-' + new Date().toString()"` – Kalnode Jun 17 '22 at 17:14
  • @Kalnode this way you're basically getting rid of any way for vue to optimize it. In this case you can also just use no key at all... – Simon Jul 07 '23 at 08:16
  • Your example has a bug, you're not reacting to prop changes... `key` doesn't fix that bug but forces vue to remount the component. – Simon Jul 07 '23 at 08:18
5

From the Vue docs (emphasis mine): https://vuejs.org/guide/essentials/list.html#maintaining-state-with-key

To give Vue a hint so that it can track each node's identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item

If the index of any item in the array is changed (e.g. by adding/removing a new item anywhere other than the end of the array), then Vue will lose track of the item.

For example:

let data = [A, B, C, D]
<div v-for="(item, index) in data" :key="index">

Vue tracks each item like this:

A: 0
B: 1
C: 2
D: 3

If you remove B from the array, Vue will then track each item like this:

A: 0
C: 1
D: 2

The indices of C and D have changed, so Vue has lost track of them, and will remove D from the rendered output instead of B (because from its point of view, it is the item with index 3 that was removed).

This is why the key should uniquely identify an item, which an index does not do.


However, it also means that there are some cases where using the index as the key is acceptable:

  • The array will not change
  • Or the array will only have items added/removed at the end and will not be reordered
  • Or the items are all identical

If you cannot use the index as a the key, but cannot uniquely identify items (e.g. some of the items in the list are identical), a solution may be to use lodash's uniqueId():

<div v-for="item in data" :key="uniqueId()">

or as mentioned here something like

<div v-for="(item, index) in data" :key="`${item.someProperty}-${index}`">
binaryfunt
  • 6,401
  • 5
  • 37
  • 59
  • I appreciate acceptable uses of index as key. – Jumi Mar 07 '23 at 08:40
  • `uniqueId()`wont really work with eslint. You will run into ```error Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive vue/valid-v-for``` – disservin Jul 24 '23 at 14:25
  • @disservin I've opened an issue for that: https://github.com/vuejs/eslint-plugin-vue/issues/2246 – binaryfunt Jul 24 '23 at 15:58
  • I've added an alternative to `uniqueId()` as per the suggestion they responded with in that issue – binaryfunt Jul 25 '23 at 10:00
-5

console.clear()

Vue.component("item", {
  props: ["value"],
  data() {
    return {
      internalValue: this.value
    }
  },
  template: `<li>Internal: {{internalValue}} Prop: {{value}}</li>`
})


new Vue({
  el: "#app",
  data: {
    items: [{name:'a'},{name:'b'},{name:'c'}]
  },
  methods: {
    addValue() {
      this.items = [{name:'a'},{name:6},{name:'b'},{name:'c'}];
    }
  }
})
<script src="https://unpkg.com/vue@2.2.6/dist/vue.js"></script>
<div id="app">
  {{items}}
  <ul>
    <item v-for="i in items" :value="i.name" :key="i"></item>
  </ul>
  <button @click="addValue">AddValue</button>
  <ul>
    <item v-for="(i, index) in items" :value="i.name" :key="index"></item>
  </ul>
</div>

To be more clear