Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue component not immediately updating after associated data changes

Tags:

vue.js

vuejs2

I am still new to Vue and struggle with having a component update its contents as soon as connected data has been updated. The component will show the changed content as soon as it has been re-rendered though.

Component

Vue.component('report-summary', {
  props: ['translation', 'store'],
  watch: {
    'store.state.currentModel.Definition': {
      handler: function(change) {
        console.log('Change detected', change);
      },
      deep:true
    }
  },
  template: '<div>
    '<div class="alert alert-secondary" role="alert">' +
    '<h5 class="border-bottom border-dark"> {{ translation.currently_defined }}</h5>' +
    '<div>{{ store.state.currentModel.Definition.length }} {{translation.elements }}</div>' +
    '</div>' +
    '</div>'
});

store is passed in as a property in the HTML of the page:

<report-summary :translation="t" :store="store"></report-summary>

store itself is a Vuex.Store:

let store = new Vuex.Store({
  state: {
    t: undefined,
    currentModel: undefined
  },
  mutations: {
    storeNewModel(state) {
      let model = CloneFactory.sealedClone(
        window.Project.Model.ReportTemplateModel);
      model.Header = CloneFactory.sealedClone(
        window.Project.Model.ReportTemplateHeaderModel);
      state.currentModel = model;
    },
    storeNewModelDefinition(state, definition) {
     state.currentModel.Definition.push(definition);
    }
  }
});

The currentModel element gets initialized by invoking store.commit('storeNewModel'); before any data is stored in the model's Definition attribute.

The definition's content gets updated by using store.commit('storeNewModelDefinition') in a loop:

for (let c in this.$data.currentDefinition.charts) {
  store.commit('storeNewModelDefinition', c);
}

When store.commit('storeNewModelDefinition', c); is called the store updates as expected:

Vuex%20Store|325x190

But the component won't react to the changed data:

Not%20updated|553x110

Then when I navigate away (by changing the view which hides the embedded component) and again navigate to the view the content got updated:

updated|476x112

In the Console window I see that the watcher hasn't been triggered at any point in time:

console|681x133

What am I missing here? Thanks a lot for opening my eyes in advance.

Using an EventBus

Switching the watch to an event bus listener also didn't work as planned.

This is the initialization of the EventBus:

window.eventBus= new Vue(); 

I added this to my Component:

  created: function() {
    window.eventBus.$on('report-summary-update', function() {
      this.$nextTick(function(){ this.$forceUpdate(); });
    });
  }

And emit the event after adding or removing elements from the list:

window.eventBus.$emit('report-summary-update');

When setting a breakpoint in the Debugger watching the this.$forceUpdate(); I see that it's also being called but the UI still will show the old content without any changes. I am really lost right now.


P.S. I cross-posted this on the Vue forum but as the community is rather small hope to receive some feedback here.

like image 497
SaschaM78 Avatar asked Nov 29 '19 15:11

SaschaM78


People also ask

How do I force update my Vue component?

The best way to force Vue to re-render a component is to set a :key on the component. When you need the component to be re-rendered, you just change the value of the key and Vue will re-render the component.

Does Vue automatically update?

One of Vue's best features is reactivity. You change a variable, and Vue automatically updates what is rendered to the page. However, not everything in Vue is reactive by default.

How does Vue reactivity work?

Vue's reactivity system works by deeply converting plain JavaScript objects into reactive proxies. The deep conversion can be unnecessary or sometimes unwanted when integrating with external state management systems (e.g. if an external solution also uses Proxies).

How do I make my Vue component dynamic?

To make the component dynamic, we can bind it to a set property with the v-bind directive. Your component is now bound with the component property in the data. If you switch the component to Test2 , it will automatically mount the Test 2 component. Test it out on your browser.


1 Answers

Pushing data to a nested array always causes such issues where the component does not update.

Try adding a getter to your store and use that getter to get data from the store

let store = new Vuex.Store({
  state: {
    t: undefined,
    currentModel: undefined
  },
  mutations: {
    storeNewModel(state) {
      let model = CloneFactory.sealedClone(
        window.Project.Model.ReportTemplateModel);
      model.Header = CloneFactory.sealedClone(
        window.Project.Model.ReportTemplateHeaderModel);
      state.currentModel = model;
    },
    storeNewModelDefinition(state, definition) {
     state.currentModel.Definition.push(definition);
    }
  },
  getters:{
    definitions(state){
      return state.currentModel.Definition
    }
  }
});

In your component, you can add a computed prop that gets the data from store

Vue.component('report-summary', {
  props: ['translation', 'store'],
  computed: {
    definitions(){
     return store.getters.definitions
    }
  },
  template: '<div>
    '<div class="alert alert-secondary" role="alert">' +
    '<h5 class="border-bottom border-dark"> {{ translation.currently_defined }}</h5>' +
    '<div>{{ definitions.length }} {{translation.elements }}</div>' +
    '</div>' +
    '</div>'
});

UPDATE (30 Nov, 2019) If the above code still does not work, change your storeNewModelDefinition mutation to this:

storeNewModelDefinition(state, definition) {
     // Create a new copy of definitions
     let definitions = [...state.currentModel.Definition]

     // Push the new item
     definitions.push(definition)

     // Use `Vue.set` so that Vuejs reacts to the change
     Vue.set(state.currentModel, 'Definition', definitions);
    }
like image 53
Ankit Kante Avatar answered Oct 05 '22 01:10

Ankit Kante