Search code examples
vue.jsvuejs2v-for

VueJS v-for unwanted behaviour


I get this problem whenever I modify an array that is used to render a v-for list.

Let's say I've got a v-for list of three items:

<ul>
  <li v-for="item in items"></li>
<ul></ul>

<ul>
  <li>One</li> <!-- Has focus or a specific child component -->
  <li>Two</li>
  <li>Three</li>
</ul>

Add a new item to the items array:

<ul>
  <li>New Item</li> <!-- Focuses on this item, the child component seems to be moved here -->
  <li>One</li>
  <li>Two</li>
  <li>Three</li>
</ul>

The focus seems to move...

Please have a look at a fiddle that illustrates the problem https://jsfiddle.net/gu9wyctr/

I understand that there must be a good reason for this behaviour, but I need to manage it or avoid completely. Ideas?

EDIT:

I've just realized that my explanation is rather ambiguous. Here's an updated fiddle to illustrate the problem https://jsfiddle.net/keligijus/d1s4mjj7/

The problem is that the input text is moved to another element...

My real life example. I've got a forum-like list of posts. Each post has an input for a reply. If someone publishes a new post while other user is typing in a reply, the input that this user is typing in is moved to another post. Just like the example in the fiddle.


Solution

  • Providing key is the answer!

    https://v2.vuejs.org/v2/guide/list.html#key

    When Vue is updating a list of elements rendered with v-for, it by default uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will simply patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index" in Vue 1.x.

    This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).

    To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item. An ideal value for key would be the unique id of each item. This special attribute is a rough equivalent to track-by in 1.x, but it works like an attribute, so you need to use v-bind to bind it to dynamic values (using shorthand here):

    <li v-for="(item, index) in items" :key="'item-'+item">
      <input :id="'item-'+index" type="text" style="width:80%;">
    </li>
    

    Updated fiddle to show that this works https://jsfiddle.net/keligijus/d1s4mjj7/3/