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:
But the component won't react to the changed data:
Then when I navigate away (by changing the view which hides the embedded component) and again navigate to the view the content got updated:
In the Console window I see that the watcher hasn't been triggered at any point in time:
What am I missing here? Thanks a lot for opening my eyes in advance.
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.
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.
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.
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).
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.
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With