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.
- We click on an item - let's use
id
2 as an example. - The
removeFromList
method finds that the index of this item is actually1
and promptly removes this item from the array. - 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 index1
(cycling the array), looks like that didn't change. It goes on to index1
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 index2
and the same problem occurs, and so on. Vue effectively re-render the who list. - On the other hand,
<transition-group>
is trying its best to catch up with the DOM changing and thestate
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)
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... )