VueJS v - for unwanted behavior

I get this problem whenever I change the array that is used to display the v-for list.

Let's say I have a v-list of three elements:

<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 new item to array of items:

<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 be moving ...

Please take a look at the fiddle that illustrates the issue https://jsfiddle.net/gu9wyctr/

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

EDIT:

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

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

My real life example. I have a list of posts similar to the forum. Every message has a response to reply. If someone posts a new post and another user enters a response, the input that user enters is carried over to the other post. As in the violin example.

+3


source to share


2 answers


Key provision - this is the answer!

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

When Vue updates the list of elements created with v-for, it defaults to the "in place of the patch" strategy. If the order of the data elements has changed, instead of moving the DOM elements according to the order of the elements, Vue simply fixes each element in place and makes sure it reflects what should be displayed at that particular index. This is similar to the behavior of track-by = "$ index" in Vue 1.x.

This mode is effective by default, but only suitable when the list rendered output is independent of the child component state or temporary DOM state (such as form input values).

In order to give Vue a hint so that it can keep track of each node identity and thus reuse and reorder existing elements, you need to provide a unique key attribute for each element. The ideal value for the key would be a unique identifier for each item. This special attribute is roughly 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 the 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 this works https://jsfiddle.net/keligijus/d1s4mjj7/3/

+3


source


Try the following:



var app = new Vue({
  el: '#app',
  data: {
    messages: [
      { message: 'Hello Vue!', id: 0 },
      { message: 'Hello Vuex!', id: 1 },
      { message: 'Hello VueRouter!', id: 2 }
    ],
    msg: null,
    focus: 'item-1'
  },
  mounted () {
  	document.getElementById(this.focus).focus()
  	setTimeout(() => {
    	this.messages.unshift({ message: 'Focus moves!', id: 3 })
    }, 2000)
  	setTimeout(() => {
    	this.messages.unshift({ message: 'Moves again...', id: 4 })
      this.msg = `I suppose this happens because of the way DOM is updated and I understand there must a good reason for this. However I need to avoid this behaviour. How can I do this?`
    }, 4000)
  },
  updated: function () {
  	document.getElementById(this.focus).focus()
  }
})
      

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
  <ul>
    <li v-for="(message, index) in messages">
      <input :id="'item-'+message.id" type="text" v-model="message.message" style="width:80%;">
    </li>
    <li v-if="msg">{{msg}}</li>
  </ul>
</div>
      

Run codeHide result


Basically I make the id the same even when new elements are added and then I can track the focused element and focus again even after the update.

+1


source







All Articles