In Vue, when do I actually need the :key attribute and why?
Tags: vue, key, javascript
Published: 2019-10-14 07:13:08

This article was originally published at [https://www.telerik.com/blogs/in-vue-when-do-i-actually-need-the-key-attribute-and-why](https://www.telerik.com/blogs/in-vue-when-do-i-actually-need-the-key-attribute-and-why)

---

## The problem

So you find yourself writing a Vue app. Maybe you are using the amazing [Vue CLI 3](https://cli.vuejs.org), and got yourself a nice set up that gives you some eslint errors and hints.

All of a sudden you are minding your own business, eating your avocado toast and morning _latte_ and the squiggly lines catch your attention. That last `v-for` loop seems to be **wrong**?

Maybe you decide to ignore it and to carry on with your avocado induced nirvana but then it strikes you one more time. Console errors 🚧. You panic a little, Vue is demanding it. `:key` has not been set.

You give in to your instincts and add a `:key` based on the array's loop, you know it has to be unique. Sweet relief, the errors are gone and you can move on seeking the betterment of humanity through javascript.

Except, what exactly does this all even mean? And why should you care?

## Understanding the basics of `:key` 🔑

As suggested by [the official docs](https://vuejs.org/v2/api/#key) the `key` special attribute is used by Vue as a _hint_ for it to understand what exactly it is you're trying to accomplish.

But what exactly does it mean to say that it is only a _hint_? Vue is **smart**. If you don't add the `:key` attribute to your `v-for` loop the app will not come crashing down on itself in fiery wrath. It is not actually even _required_ that you add it.

When `:key` is missing, Vue will use internal functions or algorithms to try to figure out the best way to avoid moving DOM elements around. Less movement means less re-rendering and better performance.

This process, however, has the faults of being generic and automated, and even though it is _GOOD_ at its job - you, the programmer 💻, will probably know better on how the performance and DOM manipulation should happen. This implies that you understand the attribute in order to actually get the desired outcome.

So why do we get _eslint_ warnings and console warnings? ⚠️

There are particular cases where the use of `key` is _vital_, either to provide data consistency and not lose values (for example in forms) or to attain object constancy in animations. More on these later.

My personal suggestion, in this case, is that you continue to use it in every case, but, with a better understanding of _what_ it will accomplish and _why_ you need to add it.

Let's talk specifics.

## Preserving state

When working with HTML elements that have a _state_ in our `v-for` loops we have to be careful that that state does not get destroyed when the DOM is re-rendered.

Elements like `<input>`, `<select>` and `<textarea>` all hold an internal state that captures the `value` of that element. When Vue's virtual DOM is modified because our reactive data changed, we can have cases where the DOM that holds our looped elements can be completely or partially destroyed if the `key` is not properly set.

```html
<!-- Wrong -->
<input v-for="input in myForm" />

<!-- Right -->
<input v-for="input in myForm" :key="unique-condition" />
```

This problem will lead to a situation VERY hard to debug if you don't know exactly what you are looking for, because it may simply "look" like there's a problem with how the data you are gathering from the form is being magically deleted.

This same case applies to looping through elements that make use of the `v-html` directive. The `key` property will assist Vue in making a better job of recognizing each element on the list, and not destroying elements potentially that could hold elements with a state within them.

```html
<!-- Wrong -->
<span v-html="<input />" v-for="item in items" />

<!-- Right -->
<span v-html="<input />" v-for="item in items" :key="unique-condition" />
```

This of course also applies to looping custom made components that hold state, the same rule of thumb applies. If the `key` is not defined, you are at risk of data and state being destroyed due to a re-render of the DOM.

Finally, keep an eye out for `v-for` loops that cycle on an element that contains a stateful element WITHIN it. The same problem can obviously occur.

```html
<!-- Wrong -->
<div v-for="item in items">
    <input>
</div>

<!-- Right -->
<div v-for="item in items" :key="unique-condition">
    <input>
</div>
```

## Object Constancy

Animations are not just a pretty way to move data around 🎬, they convey important information to our users of what is happening to the information they are looking at. When an object moves around the screen, slides, or fades, we expect that object to be consistent and easy to track as it conveys the information it’s trying to show us.

### Wait, what?

Imagine a mobile menu sliding in from the left 📲 after you’ve touched on a hamburger 🍔 icon (🤔 We have hamburger and kebab menus, let's make 🥑 menu happen team!). 

It transitions smoothly into halfway through the screen and clearly displays the options that you, the user, has for navigating the webpage. However, when you touch on one of the menu items, the menu magically snaps to the right side of the screen and disappears to the right hand of the phone.

Confused, you tap the hamburger icon and the menu reappears from the left side of the screen once again. 🤷‍

This is a great example of a lack of object constancy. We expect the virtual object of the menu to be “hidden” on the same side of our phone, and that it “slides in” to the viewport when we press the button. When this animation is not consistent or clear, it creates a bad user experience and also causes problems tracking the information.

This is a VERY simple example, but what happens when we take it a step further and have a list of items that are trying to convey for example some charted data, or a todo list. When one of those items slides to the left or fades out, we expect THAT item to disappear. If for some unknown reason to the user, the object magically disappeared, and then another one slid to the left it would create confusion and the animation -  rather than serving a strong visual cue, it would create discomfort and confusion.

### A real-world example

> Talk is cheap. Show me the code. - Linus Torvalds

I’ve created a simplified example of the last user interaction that I described so that you can see it in action.

https://codesandbox.io/s/jjlwv87w1v

Open up the Sandbox, and look at the `App.vue` file.

We have two lists of items been fed by the same pool of data, a property called `list`.

On the top list, we create a `v-for` loop that is using the unique `id` property of each item as a way to track the uniqueness of each of the list items - as usually suggested by the compiler, and to increase DOM performance.

On the bottom list, we are using a common “*hack*“, to use the array’s `index` as a way to loop our items and satisfy the `:key` warning.

I’m not going to touch deeply on the DOM implications of using the `index` as a key, for it can *sometimes* be the correct answer if you know exactly what you are doing regarding index management. But instead, let’s focus on the implications it has for UX.

Both lists are wrapped inside a `<group-transition>` component that will allow us to visually identify what is happening. Go ahead and play around with the top list, click around a few objects, and then hit the `reset` button. Smooth, right? The object you click is the one that is sliding away. Mission accomplished.

Go ahead and click around the second list now. I don’t know about you, but to me, this seems bugged.

Chris Fritz has an amazing example of how fluid animations can give you an intuitive user experience. Be sure to check it out [in this fiddle](https://jsfiddle.net/chrisvfritz/sLrhk1bc/). Also, try playing around with the `:key`, if you break the system then the numbers will simply stop animating.

Keep in mind for this last example that `<group-transition>` actually throws a warning if you remove `key`, and also the rendering will completely break.

Try adding an index to the `v-for` loop and setting it as the value for the `:key`, as some people do to "satisfy" the condition and remove the warning.

### Breaking things apart

What exactly is happening here that breaks our *object constancy* in the second example? 🔎

When we click on one of the items to trigger the `removeFromList` method, Vue does a couple of things on the background. First of all, the method updates the `array` that holds our `list` by calling the `splice` method on the `item`'s index.

Once the `list` 📝 has been updated however, Vue has to re-render the DOM in order to *react* to the changes in the state, this is the core of Vue's reactivity.

Usually, Vue would know that for a `v-for` loop, it needs to figure out which element it needs to update via the `key`, this is what you already know. However, because of the `<transition-group>` Vue keeps a partial state copy to perform the animations while the elements get removed from the screen, **even though** this element doesn't exist any longer on the actual component's `state`.
When we use `:key` with the object's id on the first example, Vue has an *exact* reference to what we are trying to accomplish, because this particular `item` has a *unique* way to identify itself. So when Vue needs to remove it, both from the state, and from the animation it can tell exactly which one it needs to work with.

When we use `:key` with the index however, we run into a problem. Remember the step by step we just went through? Let's try that again but let's take a closer look at what the index is doing.

1. We click on an item - let's use `id` 2 as an example.
2. The `removeFromList` method finds that the index of this item is actually `1` and promptly removes this item from the array.
3. Vue knows it has to do some DOM re-rendering because the `list` got updated, and tries its best to figure out which items it has to redraw on the screen. So it starts with index `1` (cycling the array), looks like that didn't change. It goes on to index `1` and notices the content is different (what was in index 2 now is in index 1, because splice moved it all one space down). Then goes on to index `2` and the same problem occurs, and so on. Vue effectively re-render the who list.
4. On the other hand, `<transition-group>` is trying its best to catch up with the DOM changing and the `state` getting modified, and in its best attempt it "copies" the deleted item into the end of the list and animates it leaving the screen. It has *no* way to know how to re-order its internal state to accommodate for the index changes in the state.

## Wrapping up

The `key` attribute has a lot more under the hood than it appears. Now that you understand exactly what it is trying to accomplish, and the reasons behind the "magic", you can make better calls when developing your loops - and obtain more granular control over your application and how it performs. 💪

As always, thanks for reading and share with me your thoughts on Twitter at [@marinamosti](https://twitter.com/marinamosti) 

PS. All hail the magical avocado 🥑

PSS. ❤️🔥🐶☠️