Why you should use the key directive in Vue.js with v-for
In any web or mobile app, we see lists every day. All those Facebook, Twitter, and Instagram feed we keep scrolling — they are all some form of lists.
Implementing lists in Vue is like a piece of cake, thanks to the v-for directive. It lets you iterate over arrays and objects and represent each element just the way you want.
Here is an example of very basic list rendering in Vue.
Although it is pretty straightforward, there can be some cases where you will end up scratching your head while everything seems to look normal. This may happen if you do not use the :key directive along with the v-for directive. Before I explain further, let me clarify a few things that will help you understand the bug and it's solution better.
Application state and DOM state
To understand the bug, we need to know the distinction between an application state and a temporary DOM state. Let us look at an example.
Here, I have created a list of tweets with input boxes where you can comment on the tweet (the comment box is a dummy created for illustration).
In the example above, a tweet-box component is displaying its information from the data that is provided to the component as an array of tweets. This part is rendering data directly from the application state (the tweets array).
The other part of the component tweet-box is an input box, which is not tied to any data within the Vue instance and hence is independent of the application state. So, if you type something into the comment box and leave it right there, the text that you just entered is stored temporarily into the DOM state.
In-place patch strategy
By now, we have seen simple rendering lists in Vue.js, and also have seen the distinction between application state and DOM state. Now we need to understand something called "in-place patch strategy" that Vue uses to render lists.
Vue's documentation describes, "If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index." Let's dig deeper.
Vue renders lists in such a way that whenever there are dynamic changes in the UI, for example, the order of the list changes, Vue will just update the data in the DOM of each element of that list. Instead of moving HTML chunks of each element of that list, Vue will just update the current application state into the DOM, by updating the DOM element's state.
This shows Vue prefers to reuse as much of the DOM as it can and try to make the manipulations at the data level and not at the DOM level. This way Vue improves the performance overall but, this can cause issues when there are such dynamic changes in UI, and the data of the DOM elements are dependent on child components or temporary DOM state. This behavior is well explained in the next section.
Updating the order of elements
By now we know that if there is a change in the order of list elements, then Vue will opt to change the data of those elements rather than moving the order of those DOM elements.
So, to see that in action, I have added a Shuffle button in the previous Tweets example. Whenever I click the shuffle button, the order of the tweets changes, just as we talked. You can try it for yourself here,
But here, is something that I want you to see in order to understand the problem that we talked about earlier.
Let's just write a comment in the any of the tweet's comment box, and then click Shuffle button.
You can see, that the comment that I wrote for a certain tweet will not move according to the shuffling of the tweets.
Why does this happen?
Quick answer: in-place patch strategy.
The comment belongs to the temporary DOM state, which basically means it belongs to the DOM and is not bound to any data that Vue recognizes. We already know that when the order of elements change in Vue, only the data of those elements changes in the DOM, and not the DOM elements itself. You can actually see that happening here.
To explain the scenario better, let's say we have two elements in a list, X and Y, and each of these elements have some data in their temporary DOM state. Now, if we shuffle the order of X and Y, then the changes will only reflect in the part of DOM which is bound to X and Y.
This happens because Vue has no way to bind whatever is in the DOM state of an element, to the application state of that element.
So, let's see how that can be solved.
Solution (Using the :key attribute)
The only solution to this kind of a problem is to add the :key attribute with v-for like this,
We have to assign a unique id of some sort to each element of the list. In this case, we are assigning some id to each tweet, and it is unique. Here is the same example of tweets, after applying the :key attribute,
Let's try to write a comment and shuffle again.
Now, you can clearly see that on shuffling, the comment that was written for a certain tweet is sticking to it even if the order changes.
So, what happened differently, here?
Let me explain it with the same example of X and Y elements. So, if we assign unique ids to those X and Y elements (using :key attribute) Vue starts to recognize each element and it's DOM state as a unique instance in itself. This basically means that now whatever is in the temporary DOM state of X and Y is bound to the application state of X and Y.
So, now if we swap the order of X and Y, what Vue sees is the swapping of the unique element that Vue has identified through those unique ids.
Conclusion
This may not seem like a huge deal right now, but just imagine that you are rendering lists in your Vue app and things have gone pretty complex. Then, if you start seeing weird behaviors occurring, it gets pretty difficult to identify what's going wrong, even though the solution is as simple as adding a :key attribute.
This is why it is always recommended to use :key attribute along with v-for. For almost all the cases, you will not see any problems occurring if you are not using :key but in certain times where the list depends on DOM state and/or the child component's state, then this can cause some funny behaviors to occur.