DEV Community

Marina Mosti
Marina Mosti

Posted on

In Vue, when do I actually need the :key attribute and why?

This article was originally published at 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, 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 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.

<!-- 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.

<!-- 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.

<!-- 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. 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

PS. All hail the magical avocado 🥑

PSS. ❤️🔥🐶☠️

Oldest comments (1)

Collapse
 
sdevore profile image
Sam DeVore • Edited

another place where it can be important is if you have a collection of divs with v-if, v-else and/or v-else-if and you have them wrapped in a transition otherwise the transition isn't fired and the elements just swap. This one bit me more then once when starting out with Vue (see the note at vuejs.org/v2/guide/transitions.htm... )