{"id":269431,"date":"2018-04-11T06:56:15","date_gmt":"2018-04-11T13:56:15","guid":{"rendered":"http:\/\/css-tricks.com\/?p=269431"},"modified":"2018-04-11T06:56:15","modified_gmt":"2018-04-11T13:56:15","slug":"list-rendering-and-vues-v-for-directive","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/list-rendering-and-vues-v-for-directive\/","title":{"rendered":"List Rendering and Vue\u2019s v-for Directive"},"content":{"rendered":"

List rendering is one of the most commonly used practices in front-end web development. Dynamic list rendering is often used to present a series of similarly grouped information in a concise and friendly format to the user. In almost every web application we use, we can see lists<\/em> of content in numerous areas of the app. <\/p>\n

In this article we’ll gather an understanding of Vue\u2019s v-for<\/code> directive in generating dynamic lists, as well as go through some examples of why the key<\/code> attribute should be used when doing so.<\/p>\n

Since we’ll be explaining things thoroughly as we start to write code, this article assumes you\u2019ll have no or very little knowledge with Vue (and\/or other JavaScript frameworks).<\/p>\n

<\/p>\n

Case Study: Twitter<\/h3>\n

We\u2019re going to use Twitter<\/a> as the case study for this article.<\/p>\n

When logged in and in the main index route of Twitter we\u2019re presented with a view similar to this:<\/p>\n

\"A<\/figure>\n

On the homepage, we\u2019ve become accustomed to seeing a list of trends, a list of tweets, a list of potential followers, etc. The content displayed in these lists depends on a multitude of factors—our Twitter history, who we follow, our likes, etc. As a result, we can say all this data is dynamic<\/em>.<\/p>\n

Though this data is dynamically obtained, the way<\/em> this data is shown remains the same. This is in part due to using reusable web components<\/strong>.<\/p>\n

For example; we can see the list of tweets as a list of single tweet-component<\/code> items. We can think of tweet-component<\/code> as a shell that takes data of sorts, such as the username, handle, tweet and avatar, among other pieces, and that simply displays those pieces in a consistent markup.<\/p>\n

\"A<\/figure>\n

Let\u2019s say we wanted to render a list of components (e.g. a list of tweet-component<\/code> items) based on a large data source obtained from a server. In Vue, the first thing that should come to mind to accomplish this is the v-for<\/code><\/strong> directive.<\/p>\n

The v-for directive<\/h3>\n

The v-for<\/code> directive is used to render a list of items based on a data source. The directive can be used on a template element and requires a specific syntax along the lines of:<\/p>\n

\"A<\/figure>\n

Let\u2019s see an example of this in practice. First, we\u2019ll assume we\u2019ve already obtained a collection of tweet data:<\/p>\n

const tweets = [\r\n  {\r\n    id: 1,\r\n    name: 'James',\r\n    handle: '@jokerjames',\r\n    img: 'https:\/\/semantic-ui.com\/images\/avatar2\/large\/matthew.png',\r\n    tweet: \"If you don't succeed, dust yourself off and try again.\",\r\n    likes: 10,\r\n  },\r\n  { \r\n    id: 2,\r\n    name: 'Fatima',\r\n    handle: '@fantasticfatima',\r\n    img: 'https:\/\/semantic-ui.com\/images\/avatar2\/large\/molly.png',\r\n    tweet: 'Better late than never but never late is better.',\r\n    likes: 12,\r\n  },\r\n  {\r\n    id: 3,\r\n    name: 'Xin',\r\n    handle: '@xeroxin',\r\n    img: 'https:\/\/semantic-ui.com\/images\/avatar2\/large\/elyse.png',\r\n    tweet: 'Beauty in the struggle, ugliness in the success.',\r\n    likes: 18,\r\n  }\r\n]<\/code><\/pre>\n

tweets<\/code> is a collection of tweet<\/code> objects with each tweet<\/code> containing details of that particular tweet—a unique identifier, the name\/handle of the account, tweet message, etc. Let\u2019s now attempt to use the v-for<\/code> directive to render a list of tweet components based on this data.<\/p>\n

First and foremost, we\u2019ll create the Vue instance—the heart of the Vue application. We\u2019ll mount\/attach our instance to a DOM element of id app<\/code> and assign the tweets<\/code> collection as part of the instance\u2019s data object.<\/p>\n

new Vue({\r\n  el: '#app',\r\n  data: {\r\n    tweets\r\n  }\r\n});<\/code><\/pre>\n

We\u2019ll now go ahead and create a tweet-component<\/code> that our v-for<\/code> directive will use to render a list. We\u2019ll use the global Vue.component<\/code> constructor to create a component named tweet-component<\/code>:<\/p>\n

Vue.component('tweet-component', {\r\n  template: `  \r\n    <div class=\"tweet\">\r\n      <div class=\"box\">\r\n        <article class=\"media\">\r\n          <div class=\"media-left\">\r\n            <figure class=\"image is-64x64\">\r\n              <img :src=\"tweet.img\" alt=\"Image\">\r\n            <\/figure>\r\n          <\/div>\r\n          <div class=\"media-content\">\r\n            <div class=\"content\">\r\n              <p>\r\n                <strong>{{tweet.name}}<\/strong> <small>{{tweet.handle}}<\/small>\r\n                <br>\r\n                {{tweet.tweet}}\r\n              <\/p>\r\n            <\/div>\r\n              <div class=\"level-left\">\r\n                <a class=\"level-item\">\r\n                  <span class=\"icon is-small\"><i class=\"fas fa-heart\"><\/i><\/span>\r\n                  <span class=\"likes\">{{tweet.likes}}<\/span>\r\n                <\/a>\r\n              <\/div>\r\n          <\/div>\r\n        <\/article>\r\n      <\/div>\r\n    <\/div>  \r\n  `,\r\n  props: {\r\n    tweet: Object\r\n  }\r\n});<\/code><\/pre>\n

A few interesting things to note here. <\/p>\n

    \n
  1. The tweet-component<\/code> expects a tweet<\/code> object prop as seen in the prop validation requirement (props: {tweet: Object}<\/code>). If the component is rendered with a tweet<\/code> prop that is not an object<\/em>, Vue will emit warnings.<\/li>\n
  2. We\u2019re binding the properties of the tweet object prop on to the component template with the help of the Mustache syntax: {{ }}<\/code>.<\/li>\n
  3. The component markup adapts Bulma\u2019s Box element<\/a> as it represents a good resemblance to a tweet.<\/li>\n<\/ol>\n

    In the HTML template, we\u2019ll need to create the markup where our Vue app will be mounted (i.e. the element with the id of app<\/code>). Within this markup, we\u2019ll use the v-for<\/code> directive to render a list of tweets. Since tweets<\/code> is the data collection we\u2019ll be iterating over, tweet<\/code> will be an appropriate alias to use in the directive. In each rendered tweet-component<\/code>, we\u2019ll also<\/em> pass in the iterated tweet<\/code> object as props for it to be accessed in the component.<\/p>\n

    <div id=\"app\" class=\"columns\">\r\n  <div class=\"column\">\r\n    <tweet-component v-for=\"tweet in tweets\" :tweet=\"tweet\"\/>\r\n  <\/div>\r\n<\/div><\/code><\/pre>\n

    Regardless of how<\/em> many more tweet objects would be introduced to the collection; or how they\u2019ll change over time—our set up will always render all the tweets in the collection in the same markup we expect.<\/p>\n

    With the help of some custom CSS, our app will look something like this:<\/p>\n

    See the Pen Simple Twitter Feed #1<\/a> by Hassan Dj (@itslit<\/a>) on CodePen<\/a>.<\/p>\n

    Though everything works as expected, we may be prompted with a Vue tip in our browser console:<\/p>\n

    [Vue tip]: <tweet-component v-for=\"tweet in tweets\">: component lists rendered with v-for should have explicit keys...<\/code><\/pre>\n

    You may not be able to see the warning in the browser console when running the code through CodePen.<\/p>\n

    Why is Vue telling us to specify explicit keys in our list when everything works as expected?<\/p>\n

    key<\/h3>\n

    It\u2019s common practice to specify a key attribute for every iterated element within a rendered v-for<\/code> list. This is because Vue uses the key<\/code> attribute to create unique bindings for each node\u2019s identity<\/strong>.<\/p>\n

    Let\u2019s explain this some more—if there were any dynamic UI changes to our list (e.g. order of list items gets shuffled), Vue will opt towards changing data within each element instead<\/em> of moving the DOM elements accordingly. This won\u2019t be an issue in most cases. However, in certain instances where our v-for<\/code> list depends on DOM state and\/or child component state, this can cause some unintended behavior.<\/p>\n

    Let\u2019s see an example of this. What if our simple tweet component now contained an input field that will allow the user to directly respond to the tweet message? We\u2019ll ignore how this response could be submitted and simply address the new input field itself:<\/p>\n

    \"A<\/figure>\n

    We\u2019ll include this new input field on to the template of tweet-component<\/code>:<\/p>\n

    Vue.component('tweet-component', {\r\n  template: `\r\n    <div class=\"tweet\">\r\n      <div class=\"box\">\r\n        \/\/ ...\r\n      <\/div>\r\n      <div class=\"control has-icons-left has-icons-right\">\r\n        <input class=\"input is-small\" placeholder=\"Tweet your reply...\" \/>\r\n        <span class=\"icon is-small is-left\">\r\n          <i class=\"fas fa-envelope\"><\/i>\r\n        <\/span>\r\n      <\/div>\r\n    <\/div>\r\n  `,\r\n  props: {\r\n    tweet: Object\r\n  }\r\n});<\/code><\/pre>\n

    Assume we wanted to introduce another new feature into our app. This feature would involve allowing the user to shuffle a list of tweets randomly.<\/p>\n

    To do this; we can first include a \u201cShuffle!\u201d button in our HTML template:<\/p>\n

    <div id=\"app\" class=\"columns\">\r\n  <div class=\"column\">\r\n    <button class=\"is-primary button\" @click=\"shuffle\">Shuffle!<\/button>\r\n    <tweet-component  v-for=\"tweet in tweets\" :tweet=\"tweet\"\/>\r\n  <\/div>\r\n<\/div><\/code><\/pre>\n

    We\u2019ve attached a click event listener on the button element to call a shuffle<\/code> method when triggered. In our Vue instance; we\u2019ll create the shuffle<\/code> method responsible in randomly shuffling the tweets<\/code> collection in the instance. We\u2019ll use lodash\u2019s _shuffle<\/a> method to achieve this:<\/p>\n

    new Vue({\r\n  el: '#app',\r\n  data: {\r\n    tweets\r\n  },\r\n  methods: {\r\n    shuffle() {\r\n      this.tweets = _.shuffle(this.tweets)\r\n    }\r\n  }\r\n});<\/code><\/pre>\n

    Let\u2019s try it out! If we click shuffle a few times; we\u2019ll notice our tweet elements get randomly assorted with each click.<\/p>\n

    See the Pen Simple Twitter Feed #2<\/a> by Hassan Dj (@itslit<\/a>) on CodePen<\/a>.<\/p>\n

    However, if we type some information in the input of each component then<\/em> click shuffle; we\u2019ll notice something peculiar happening:<\/p>\n

    \"An<\/figure>\n

    Since we haven\u2019t opted to using the key<\/code><\/strong> attribute, Vue has not created unique bindings to each tweet node. As a result, when we\u2019re aiming to reorder the tweets, Vue takes the more performant saving approach to simply change (or patch)<\/em> data in each element. Since the temporary DOM state (i.e. the inputted text) remains in place, we experience this unintended mismatch.<\/p>\n

    Here\u2019s a diagram that shows us the data that gets patched on to each element and the DOM state that remains in place:<\/p>\n

    \"A<\/figure>\n

    To avoid this; we\u2019ll have to assign a unique key<\/strong> to every tweet-component<\/code> rendered in the list. We\u2019ll use the id<\/code> of a tweet<\/code> to be the unique identifier since we should safely say a tweet\u2019s id<\/code> shouldn\u2019t be equal to that of another. Because we\u2019re using dynamic values, we\u2019ll use the v-bind<\/code> directive to bind our key to the tweet.id<\/code>:<\/p>\n

    <div id=\"app\" class=\"columns\">\r\n  <div class=\"column\">\r\n    <button class=\"is-primary button\" @click=\"shuffle\">Shuffle!<\/button>\r\n    <tweet-component  v-for=\"tweet in tweets\" :tweet=\"tweet\" :key=\"tweet.id\" \/>\r\n  <\/div>\r\n<\/div><\/code><\/pre>\n

    Now, Vue recognizes each tweet\u2019s node\u2019s identity; and thus will reorder<\/em> the components when we intend on shuffling the list.<\/p>\n

    \"An<\/figure>\n

    Since each tweet component is now being moved accordingly, we can take this a step further and use Vue\u2019s transition-group<\/code> to show<\/em> how the elements are being reordered.<\/p>\n

    To do this, we\u2019ll add the transition-group<\/code><\/a> element as a wrapper to the v-for<\/code> list. We’ll specify a transition name of tweets<\/code> and declare that the transition group should be rendered as a div<\/code> element.<\/p>\n

    <div id=\"app\" class=\"columns\">\r\n  <div class=\"column\">\r\n    <button class=\"is-primary button\" @click=\"shuffle\">Shuffle!<\/button>\r\n    <transition-group name=\"tweets\" tag=\"div\">\r\n      <tweet-component  v-for=\"tweet in tweets\" :tweet=\"tweet\" :key=\"tweet.id\" \/>\r\n    <\/transition-group>\r\n  <\/div>\r\n<\/div><\/code><\/pre>\n

    Based on the name of the transition, Vue will automatically recognize if any CSS transitions\/animations have been specified. Since we aim to invoke a transition for the movement<\/em> of items in the list; Vue will look for a specified CSS transition along the lines of tweets-move<\/code> (where tweets<\/code> is the name given to our transition group). As a result, we’ll manually introduce a .tweets-move<\/code> class that has a specified type and time of transition:<\/p>\n

    #app .tweets-move {\r\n  transition: transform 1s;\r\n}<\/code><\/pre>\n

    This is a very brief look into applying list transitions. Be sure to check out the Vue docs<\/a> for detailed information on all the different types of transitions that can be applied!<\/p>\n

    Our tweet-component<\/code> elements will now transition<\/em> appropriately between locations when a shuffle is invoked. Give it a try! Type some information in the input fields and click \u201cShuffle!\u201d a few times.<\/p>\n

    See the Pen Simple Twitter Feed #3<\/a> by Hassan Dj (@itslit<\/a>) on CodePen<\/a>.<\/p>\n

    Pretty cool, right? Without the key<\/code> attribute, the transition-group element can\u2019t be used to create list transitions<\/strong> since the elements are patched in place<\/em> instead of being reordered.<\/p>\n

    Should the key<\/code> attribute always be used? It\u2019s recommended<\/strong>. The Vue docs<\/a> specify that the key attribute should only be omitted if:<\/p>\n