One of the most confusing attributes in Vue is key.

It's not a directive like v-for.

It's not a prop, even though it looks exactly like one.

It has nothing to do with slots, lifecycle hooks, computed props or watchers.

But that doesn't mean it's not important.

The key attribute gives Vue hints

Vue does the heavy lifting of updating what's rendered to the screen. But there's only so much that Vue can know about your app and how it should do these updates.

You, as the developer, need to give Vue some hints every now and then.

The key attribute tells Vue how your data relates to the HTML elements it's rendering to the screen. When your data changes, Vue uses these keys to know which HTML elements to remove or update, and if it needs to create any new ones.

Let's say you're rendering a list of components using v-for, and those components have one or more of the following:

  • Their own local state
  • Some sort of initialization process, typically in created or mounted hooks
  • Non-reactive DOM manipulation, through jQuery or vanilla APIs

If you sort that list, or update it in any other way, you'll need to re-render some of the components in that list. But you won't want to re-render everything in the list, just the things that have changed.

This is where keys come in.

Don't use an index as the key

Many people make the mistake of using the index of the array as the key. But that's not helpful here, since that index is not tied to specific objects in our list.

I'll show you what I mean.

Here is an example list:

const people = [
  { name: 'Evan', age: 34 },
  { name: 'Sarah', age: 98 },
  { name: 'James', age: 45 },
];

We can render the list, also displaying the indexes:

<ul>
  <li v-for="(person, index) in people" :key="index">
    {{ person.name }} - {{ index }}
  </li>
</ul>

Which will display this:

Evan - 0
Sarah - 1
James - 2

If we remove Sarah, we will get this:

Evan - 0
James - 1

What's happening here?

Let's go through this line by line.

Evan stays with the same index of 0, so Evan is still using the same component after the change. Sarah had the index of 1, but now James has that index. So James took over the component that Sarah had, instead of sticking with his own component.

James had the index of 2, but since that doesn't exist after we update the list, his component is deleted.

If you're thinking that this is really complicated, you're not wrong.

Can't we just have Evan and James keep their components, and delete the component Sarah was using?

Use a unique ID as the key

If we did things properly, this is exactly what would happen. But to do this we need to use unique ids for each item, not their array index:

const people = [
  { id: 'this-is-an-id', name: 'Evan', age: 34 },
  { id: 'unique-id', name: 'Sarah', age: 98 },
  { id: 'another-unique-id', name: 'James', age: 45 },
];
<ul>
  <li v-for="person in people" :key="person.id">
    {{ person.name }} - {{ person.id }}
  </li>
</ul>

If we repeat the above experiment with the whole list, we get this output:

Evan - this-is-an-id
Sarah - unique-id
James - another-unique-id

If we remove Sarah, we will get:

Evan - this-is-an-id
James - another-unique-id

Before, when we removed Sarah from our list, Vue was confused and didn't know how to update the DOM. Now, Vue knows that it can keep the two components for Evan and James, and all it has to do is delete Sarah's.

This also works if we add a person to the list.

Vue knows that it can keep all of the existing components, and it only has to create a single new component and insert it into the correct place.

This is really useful, and helps us a lot when we have more complex components that have their own state, have initialization logic, or do any sort of DOM manipulation.