javascriptvue.jsvirtual-dom

Vue.js components internal state being reused when underlying data changes


I am working on a medium size web application based on vue.js. I've been facing several virtual DOM issues mostly related to vue instances life cycle.

The following code snippet (jsFiddle also available here) demonstrates some of these problems. The test-component vue component receives a property value and updates its internal state using that value.

Whenever the data set changes (by sorting or by new data), the properties are updated, but the internal state is not. This is pretty easy to see by running the code snippet and clicking on the buttons.

I understand the data component attribute is a function and it looks like it is invoked only when the component is created. That kind of explains what is going on. The thing is I don't know how to achieve the intended behavior which is: all the children components internal state to be updated whenever the data source changes.

  1. Is there a life cycle callback that triggers when the component is
    "refreshed"? I want a component refresh only callback. Do I need to watch for prop changes individually? That would be ugly.
  2. Is there a clean way to update the internal state when the props change?
  3. Is it possible to destroy all children components and force them to be recreated? I know this is not the best option but it would solve all my data binding problems :)
  4. How does vue decides when a component will be released or reused? In my example components go away when the data set size changes from 4 to 3, but the first 3 items internal state don't change so the first three items are being recycled.

Moving all the state to vuex would be a possible solution but some components are generic and I don't want them to contain knowledge about the vuex store details. Actually I am using vuex for most components, but for truly generic components I don't think it is a good option.

var testComponent = Vue.component('test-component', {
  template: '#component_template',
  props: ['prop1'],
  data: function(){
    return {
      internalState: this.prop1 
    }
  }
})

new Vue({
  el: "#app",
  data: function(){
    return {
      items: [
        'Item 1',
        'Item 2',
        'Item 3'
      ],
      dataToggle: true,
      sorted: true
    };
  },
  methods: {
    sortDataSet: function(){
      if(this.sorted){
        this.items.reverse();
      } else {
        this.items.sort;
      }
    }
    ,changeDataSet: function(){
      if(this.dataToggle){
        this.items = ['Item 4', 'Item 5', 'Item 6', 'Item 7'];
      } else {
        this.items = ['Item 1', 'Item 2', 'Item 3'];
      }
      
      this.dataToggle = !this.dataToggle;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.2/vue.js"></script>
<body>
  <h3>
    Components internal state is not being updated
  </h3>
  <div id="app">
    <button v-on:click="changeDataSet">
      Change data
    </button>
    <button v-on:click="sortDataSet">
      Sort data
    </button>
    <ul>
      <li v-for="item in items">
        <test-component :prop1="item">
        </test-component>
      </li>
    </ul>
  </div>
</body>

<script id="component_template" type="x-template">
	<div>Prop: {{prop1}} || Internal state: {{internalState}}</div>
</script>


Solution

  • Use a key.

    It is recommended to provide a key with v-for whenever possible, unless the iterated DOM content is simple, or you are intentionally relying on the default behavior for performance gains.

    <li v-for="item in items" :key="item">
        <test-component :prop1="item">
        </test-component>
    </li>
    

    Updated fiddle.