Component not updating when data changes

Hey guys, as part of my recent exploration of Vue, I’ve just ran into another issue that I’m hoping to resolve. I’ve currently got a component that retrieves some data from a global object. Unfortunately when this data changes the component does not reflect that change. I’ve tried using both watch and update and different variations of data insertion points but alas I can’t figure it out. The documentation seems to go through reactivity but not for components specifically. Some help would be appreciated, here’s my code…

var graphData = { 
    data1: {...}
    data2: {...}
}


Vue.component('graph', {
    props:['graphId','chartData'],
    template: '<canvas :id=graphId></canvas>',
    data: function() {
       return graphData;
    },
    methods: {
        initGraph: function (_el) {
                _el = document.getElementById(_el)
            var settlementBalanceBarChart = new Chart(_el, {
                type: "bar",
                data: this.settlementBalanceBarData,
                options: this.settlementBalanceBarOptions
            });
         }
        },

        watch:{
            graphData: function (_el) {
                alert('data changed!');
                _el = document.getElementById(this.graphId)
                _el.destroy();
            }
        },

        mounted: function(){
        this.initGraph(this.graphId)
    }
    // update: function(){
    //alert('data changed!');
    //     _el = document.getElementById(this.graphId)
    //     _el.destroy();
    // }

});

var vm = new Vue({
    el: "#app",
    //components: ['ibox','graph']
   //data: graphData

});

How are you changing that data? Reactivity happens regardless of being specific to components. It all depends how those data sets are being updated.

Can you show us how you are mutating your graphData object?

Currently I’m just changing it via the console using vanilla js notation so: graphData = ’ ';

What is the correct way of changing this?

Many thanks

Thats not going to work. Read about change detection caveats under the reactivity documentation

Vue cannot detect changes like that, by Javascripts nature, nothing specific to Vue. Things like adding new keys to an object AFTER it has been reactified are also not detected as an example, among other things.

1 Like

I read through the caveats and apparently this isn’t a change that will be detected if changed through the console or if the properties are removed or added. So how would you suggest something like this(which will be graph that updates based on on an object) detect changes which might include deletion or insertion?

Many thanks

It isnt due to the fact that you’re changing them through the console (at least I don’t think), but doing something like changing a variable to something else, say from an object, to a string isnt a detectable thing. It can detect when a value inside has changed, say a sub key, but due to javascripts observe functionality that has been dropped, you simply cant detect watching the object… itself change, hard to describe. Try something like this. Wrap your graphData in an object itself, with graphData as a sub key. Now graphData as a key:value itself will become reactive inside that object.

var singleSourceOfTruth = { graphData: {data1:[], data2:[] }

now hook that up to your data function

data () {
  return singleSourceOfTruth
}

now create a method in your component to update a value as a test and hook it up to a button, say lets push a new value to the graphData

methods: {
  updateTest() {
     this.graphData.data1.push('newValue')
  }
}

The reason when you tried to watch “graphData” and nothing happened, is because there simply was no key called “graphData” assigned to your component instance. When you did

data () {
  return graphData
}

is essentially the same thing as

data () {
  return {
    data1:[],
    data2:[]
}

Since watchers watch properties to that component instance specifically, you can see there is no variable called “graphData” on that component. You would be able to watch “data1” or “data2” for new pushes to their arrays.

Now lets say you want to add NEW data keys, say data3, data4. Well since this will happen after reactivity was performed on the “singleSourceOfTruth” object, it will not know when a new key:value has been created, as outlined in that document. So by that you would need to use the vm.set() method as outlined in the documentation.

Thanks for your time bolerodan, unfortunately I tried the solution you outlined and haven’t had any luck. The global object is changing but it doesn’t seem to be firing the watch function. I seem to have followed your approach line by line, have I missed something out?

var graphData = {
    vals: {
        settlementBalanceBarData: {
            labels: ["...", "...", "..."],               
        },
        settlementBalanceBarOptions: { ...}       
};


Vue.component('graph', {
    props:['graphId','chartData'],
    template: '<canvas :id=graphId v-on:click=updateGraph></canvas>',
    data: function() {
       return graphData;
    },
    methods: {
        initGraph: function (_el) {
                _el = document.getElementById(_el)
            var settlementBalanceBarChart = new Chart(_el, {
                type: "bar",
                data: this.vals.settlementBalanceBarData,
                options: this.vals.settlementBalanceBarOptions
            });
         },
         updateGraph: function(){
             alert('udpated')
             graphData.vals.settlementBalanceBarData.labels =  ["red", "red", "red"]
         }
        },

        watch:{
            graphData: function (_el) {
                alert('watched');
                _el = document.getElementById(this.graphId)
                _el.destroy();
                this.initGraph(_el)
            }
        },

        mounted: function(){
            this.initGraph(this.graphId)

        },
        // update: function(_el){
        //     alert('watched');
        //     _el = document.getElementById(this.graphId)
        //     _el.destroy();
        // }

});

var vm = new Vue({
        el: "#app"
        //components: ['ibox','graph']
       //data: graphData

});

I forgot to mention, when watching changes happen somewhere in a sub key of an object, you must specify the deep option and the watch syntax turns into the following

watch: {
	'graphData': {
  	    handler: function(val) { console.log('changed') },
            deep: true
        }
}

also your code you still are making the same mistake in your previous example. You do not have a variable called "graphData’ on your component. You’re looking for a key that does not exist. In your updatedGraph function, you also need to provide this.graphData when referencing the variable on your component, but oyu must first fix your original mistake.

The only variable on your component with the way you deinfed it, is “val”, Simply do a console.log(this) in the mounted function of your component and inspect whats actually going on. Maybe that will give you a better idea to see how there is no variable called “graphData” on your component.

Thanks bolerodan, following your suggestions I managed to get the watcher to work although there are a few issues. The first is that unless I specify the exact key inside the watcher, the whole app will crash and disconnect from dev tools. So this works…

    watch:{
        'vals.settlementBalanceBarData.labels':  {
            handler: function(val){
                console.log('updated');
                console.log(val);
                this.initGraph(this.graphId)
            },
            deep: true

        }
    },

but this will crash

    watch:{
        'vals.settlementBalanceBarData':  {
            handler: function(val){
                console.log('updated');
                console.log(val);
                this.initGraph(this.graphId)
            },
            deep: true

        }
    },

Any idea what’s causing this? I need to be able to watch all of the nested keys in the object.

The second issue is that although I can specify the methods to fire inside the watcher, the update method on the component does not fire. Should this not fire whenever the data has changed?

Kind regards

This is old, but it came up as a top hit on Google and is at least loosely related to my question (as a beginner) so I’m going to revive it for anyone else who might be on the same path I presently find myself.

I think the issue is that your code expects the components (this.) to have graphData, but it has only vals. Your data object only returns an object (with property vals) which in that scope, is named “graphData”.

In other words, the data object is a method, which when executed, returns an object with the property vals. The following would address this I think (I’m new to Vue and finding my bearings still too though so beware).

data: function() { return { graphData: graphData; } },

This returns your data object, with a graphData property which IS your intended data. I also think that in updateGraph(), your reference to graphData would then need to be done via this.graphData