VueJS: unable to update a component property from inside a watcher - javascript

I am building a Vue 2 Webpack application that uses Vuex. I am trying to update a component's local state by watching a computed property which is getting data from the Vuex store. This is what the inside of the <script></script> section of my component looks like:
export default {
name: 'MyComponent',
data() {
return {
// UI
modal: {
classes: {
'modal__show-modal': false,
},
tags: [],
},
};
},
computed: {
tagList() {
return this.$store.getters.tagList;
},
},
watch: {
tagList: (updatedList) => {
this.modal.tags = updatedList;
},
},
};
As you can see, I have a computed property called tagList which fetches data from the store. I have a watcher that watches tagList so that whenever the store's data changes, I can update modal.tags to the new value.
As per Vue documentation, I can call this.propertyName and update my local component state but when I call this.modal.tags = updatedList;, I get the following error:
[Vue warn]: Error in callback for watcher "tagList": "TypeError: Cannot set property 'tags' of undefined"
Why does this error occur even though it looks no different than what is in Vue.js's documentation?

Don't use arrow functions.
Change from:
watch: {
tagList: (updatedList) => {
this.modal.tags = updatedList;
},
},
To:
watch: {
tagList(updatedList) { // changed this line
this.modal.tags = updatedList;
},
},
Vue docs mention this a few times:
Don't use arrow
functions
on an options property or callback, such as created: () => console.log(this.a) or vm.$watch('a', newValue => this.myMethod()).
Since arrow functions are bound to the parent context, this will not
be the Vue instance as you'd expect, often resulting in errors such as
Uncaught TypeError: Cannot read property of undefined
or
Uncaught TypeError: this.myMethod is not a function
It is basically a context/scope issue. When using arrow functions, the this does not refer to the Vue instance, but the enclosing context of where the component was declared (probably window).

That is because of the scope issue. You are calling this. from another context. So, within arrow functions, you don't have access to vuejs data.
I suggest you change the watch to:
tagList (updatedList) {
this.modal.tags = updatedList;
},

Related

Vue 2 watch fails for mixin data property, but works if the component has the data property

I am trying to move some functionality to a vue mixin from the component, to be able to use it in multiple components.
This (simplified version of the code) works:
export default {
data() {
return {
file: {},
audioPlayer: {
sourceFile: null,
},
};
},
watch: {
'audioPlayer.SourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}
}
But if I move the audioPlayer data object to a mixin, the watch does no longer fire.
Is this expected behavior?
N.b. I resolved this by directly making the 'file' data property into a computed value, which works in this particular case, but the behavior is still strange.
You need a lowercase s. sourceFile not SourceFile
watch: {
'audioPlayer.sourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}

VueJS Watcher is not triggered while watching deep object changes

I got a codesandbox that reproduces my problem: codesandbox example watcher not triggering.
I am working on a component that relies on an object with data that can be dynamically added to the object, so for example, in a seperate .js file, I am exporting the following object:
export default {
defaultSection1: {
displayName: 'Action',
},
defaultSection2: {
displayName: 'Thriller',
},
}
I import this object in my component.
I got a debouncer setup from Lodash so that when data changes, it only fires the watcher once after two seconds. The watcher IS triggered perfectly fine for the data that is already in the object (type in the input text box in my example, and the watcher is triggered). But when I dynamically add data to the object, the watcher is not triggered at all. Only when I change routes back and forth, the data is updated, but the watcher is not triggered. Why is this? What can I do so that the watcher is triggered when data is dynamically being added to an object.
methods: {
fetchAndUpdateData(){
console.log('Fetching data...')
},
addCustomSection(){
//The watcher should be triggered when this function is called
const newSectionId = Math.floor(Math.random() * 1000);
this.data[newSectionId] = {
displayName: 'Custom'
}
}
},
computed: {
dataWatcher() {
return this.data;
},
updateData() {
return debounce(this.fetchAndUpdateData, 2000);
},
},
watch: {
dataWatcher: {
handler() {
console.log('Watcher triggered')
this.updateData();
},
deep: true,
},
},
Why is the watcher not triggered, when clearly the data is being changed?
Another thing I noticed that is quite strange, is that in Vue 3.0, the watcher IS triggered with exactly the same code.
Codesandbox in Vue 2.6.11, watcher not triggering.
Codesandbox in Vue 3.0, watcher IS triggered with exactly the same code.
In vue 2 there's reactivity issue when updating an item in an array or a nested field in an object, to solve this you've to use this.$set() method :
this.$set(this.data,newSectionId, {displayName: 'Custom'})
this issue is solved in Vue 3. and you could just do :
const newSectionId = Math.floor(Math.random() * 1000);
this.data[newSectionId] = {
displayName: 'Custom'
}

Vue.js dynamically rendering svg

I'm trying to render a svg file containing Vue.js template syntax at specific places. The file gets rendered correctly.
Upon instantiation of the inner element (the svg), the template syntax gets replaced, but a vue warning is emitted:
vue.js:597 [Vue warn]: Property or method "data" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
In the SVG there is a text containing the template syntax {{data}}
Vue.component('svg-show', {
props: {
model: {
required: true
},
compiled:null
},
render: function (h) {
return h("div",
[
h(this.compiled, {
props: {
data: this.model
}
})
]);
},
created() {
},
mounted() {
console.log("mounted");
console.log(this.model.SVG);
this.compiled = Vue.compile("<div>" + model.SVG + "</div>");
}
});
https://jsfiddle.net/dg2hkeby/10/
i also tried to use a static variable for the data property, but this did not succeed either.
How would one achieve what i'm trying to do using Vue.js?

Call to function in Vue watcher produces "is not a function" error

I'm trying to call a function in a Vue component when a prop is changed using a watcher:
props: [
'mediaUrl'
],
watch: {
mediaUrl: function() {
this.attemptToLoadImage();
}
},
medthods: {
attemptToLoadImage: function() {
console.log('Attempting to load');
}
},
However, whenever the watcher is triggered (and it is being triggered, as doing a console.log produces a result), I'm getting the following error:
[Vue warn]: Error in callback for watcher "mediaUrl": "TypeError: this.attemptToLoadImage is not a function"
I can't understand why I'm getting this error, as my code seems to be calling the function in the way demonstrated in the Vue docs: https://v2.vuejs.org/v2/guide/computed.html#Watchers.
I'm using Vue 2.4.2.
Any help would be appreciated.
You misspelled methods as medthods:
methods: {
attemptToLoadImage: function() {
console.log('Attempting to load');
}
},

Running a function in a VueJs 2 component once its loaded

How do I fetch data once a component has been mounted? I start my vue instance and then load in the component, the component template loads in fine but the function calls in mounted are never run so the stats object remains empty, in turn, causing errors in the component/template that requires the data.
So how do I run a certain function on component load?
For what its worth... the functions I want to call will all make REST requests but each component will be running different requests.
Vue.component('homepage', require('./components/Homepage.vue'), {
props: ["stats"],
mounted: function() {
this.fetchEvents();
console.log('afterwards');
},
data: {
loading: true,
stats: {}
},
methods: {
fetchEvents: function() {
this.$http.get('home/data').then(function(response) {
this.stats = response.body;
this.loading = false;
}, function(error) {
console.log(error);
});
}
}
});
const vue = new Vue({
el: '#main',
mounted: function() {
console.log('main mounted');
}
});
You are already doing it fine by putting all the initialization stuff into mounted. The reason your component is not refreshing is probably because of binding of this, as explained below:
In your fetchEvents function, your $http success handler provides a response, which you are attempting to assign to this.stats. But it fails because this points to that anonymous function scope and not to Vue component.
To overcome this issue, you may use arrow functions as shown below:
fetchEvents: function() {
this.$http.get('home/data').then(response => {
this.stats = response.body;
this.loading = false;
}, error => {
console.log(error);
});
}
Arrow functions do not create its own scope or this inside. If you use this inside the arrow function as shown above, it still points to Vue component, and therefore your component will have its data updated.
Note: Even the error handler needs to use arrow function, so that you may use this (of Vue component) for any error logging.

Categories