`This` is undefined in vue.js watcher - vue.js

I have component with watcher
props: {
propShow: { required: true, type: Boolean }
},
data() {
return {
show: this.propShow
}
},
watch: {
propShow: {
handler: (val, oldVal) => {
this.show = val;
}
}
}
Whenever parent component changes propShow this component must update it's show property. This component also modifies show property, that's why I need both: show and propShow, because Vue.js does not allow to change properties directly.
This line
this.show = val;
causes error
TypeError: Cannot set property 'show' of undefined
because this inside handler is undefined.
Why?

You will have to use function syntax here, as warned in the docs here:
Note that you should not use an arrow function to define a watcher (e.g. searchQuery: newValue => this.updateAutocomplete(newValue)). The reason is arrow functions bind the parent context, so this will not be the Vue instance as you expect and this.updateAutocomplete will be undefined.
So your code should be:
watch: {
propShow: {
handler: function(val, oldVal) {
this.show = val;
}
}
}

"function" is not es6 code, you should better write:
watch: {
propShow: {
handler(val, oldVal) {
this.show = val;
}
}
}

Related

Computed properties are not reacting to state change

I am working on a product overview page, that send out an API-call based on the current Category you are looking at:
store.dispatch("tweakwise/fetchAPIAttributesLayeredNavigation", {
tweakwiseCategory,
this.pageNumber,
}
In my Store, the data from this API-call will be set in the following VueX Store State:
this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes: []
I want to react to this data in my front-end but my Computed methods do not seem to react to this change. As you can also see in the function below I added a Catch to prevent a "Non defined" error. The function, however, will not be called after the state has been set.
This computed property is also added to the Mount() op the component
computed: {
initialFetchProducts() {
this.fetchProducts(
this.$store.state.tweakwise?.tweakwiseLayeredNavigationAttributes || []
);
},
},
make computed property for state you want to watch,
than create watch() for this prop. In watch you can react on computed property change.
<template>
<div v-for="product in products"></div>
</template>
<script>
export default {
data: {
return {
products: [],
}
},
computed: {
tweakwiseLayeredNavigationAttributes() {
return this.$store.state.tweakwise.tweakwiseLayeredNavigationAttributes;
},
},
watch: {
// on every tweakwiseLayeredNavigationAttributes change we call fetchProducts
tweakwiseLayeredNavigationAttributes: {
handler(newValue, oldValue) {
this.fetchProducts(newValue);
},
deep: true, // necessary for watching Arrays, Object
immediate: true, // will be fired like inside mounted()
}
},
methods: {
async fetchProducts(params) {
const products = await axios.get('/api', params);
this.products = products;
}
}
};
</script>

Vue: Do watchers on deep nested objects log oldVal and newVal?

I have a watcher on a deep nested object. I am using Vue.set() to add a reactive property to the object. This is triggering the watcher but the both the newVal and oldVal console logs are showing the data with the new property added to it already rather than the oldVal showing what it was prior to adding the new property to it.
<button #click="addData">Add</button>
data() {
return {
myData: {
time: {
}
}
}
},
watch: {
myData: {
handler(newVal, oldVal) {
console.log("NEW", newVal);
console.log("OLD", oldVal);
},
deep: true
}
},
methods: {
addData() {
this.$set(this.myData.time, 'other','testing')
}
}
As mentioned by #Terry, vue doesnt keep a referency to the oldValue, see what the docs says:
Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
There is some ways you can solve that:
Using vanilla javascript
Add a computed property that returns your data property converting to a JSON string.
Use JSON.parse on your Watch function to convert the string back to a object.
data() {
return {
myData: {
time: {
}
}
}
},
computed: {
computedMyData() {
return JSON.stringify(this.myData);
}
},
watch: {
computedMyData: {
handler(newValJSON, oldValJSON) {
let newVal = JSON.parse(newValJSON),
oldVal = JSON.parse(oldValJSON);
console.log("NEW", newVal);
console.log("OLD", oldVal);
},
deep: true
}
},
methods: {
addData() {
this.$set(this.myData.time, 'other','testing')
}
}
Fiddle: https://jsfiddle.net/mLbuf6t0/
Using LODASH
Add a computed property that returns a deep clone from your data property with cloneDeep function.
data() {
return {
myData: {
time: {
}
}
}
},
computed: {
computedMyData() {
return _.cloneDeep(this.myData)
}
},
watch: {
computedMyData: {
handler(newVal, oldVal) {
console.log("NEW", newVal);
console.log("OLD", oldVal);
},
deep: true
}
},
methods: {
addData() {
this.$set(this.myData.time, 'other','testing')
}
}
Fiddle: https://jsfiddle.net/mLbuf6t0/2/

watcher in vuejs not working for props value

I am working on vue app. The issue I am facing here is that I want to run a method if props has value and in my case manager value. So I am trying something like this but the debugger is not called in the watcher. Please help me find where I am going wrong.
<script>
export default {
props: ['manager'],
watch: {
manager: function (value) {
debugger
if(value) {
this.validationManager();
}
}
},
methods: {
validationManager(){
console.log("Hello")
}
}
};
</script>
We can definetely watch the props, please try this:
watch: {
manager: {
// the callback will be called immediately after the start of the observation
immediate: true,
handler (val, oldVal) {
//do your stuff here
this.validationManager();
}
}
}
You forget the deep attribute for watcher
watch: {
manager: {
handler(value){
if(value) {
this.validationManager();
}
},
immediate: true,
deep: true,
}
}

Vue: Can watchers be used to watch a computed property?

I am trying to see if I can use a watcher method to look for a change in a computed property rather than watching the individual store properties here. Would that work or can they only be used on data properties and props?
computed: {
optionsTrue() {
return this.$store.getters.getOption1IsTrue && this.$store.getters.getOption2IsTrue
}
},
watch: {
optionsTrue(newVal) {
// would this work to check when the value of the computed property has changed or can watcher only watch data properties?
}
}
Working code
computed: {
completed: function () {
return this.required && this.textAnswer !== '';
},
},
watch: {
completed: function(val) {
console.log(val)
}
}

VueJS: How to access computed values in render function

I currently have a component with this render function:
render(createElement, context) {
return createElement(
'div', {
'class': 'sliced'
},
[
createElement('div', {
'class' : 'sliced-inner',
'style' : context.style
}
)
]
)
},
and I've added functional: true. The "style" is a computed value, but it doesn't seem to get passed with the context object. Is there any way to access computed values in a Vue render function?
A functional component has no state, so a computed property is redundant. In the following example I'm creating a header component that toggles between foo and bar when clicked:
Vue.component('message', {
render (createElement) {
return createElement('h1', {
on: {
click: event => {
return this.foo = !this.foo
}
}
}, this.fooBar)
},
computed: {
fooBar() {
return (this.foo) ? 'foo' : 'bar'
}
},
data(){
return {
foo: true
}
}
});
As you can see the header value is based on a computed, and it works fine because it is not a functional component so can have state: https://jsfiddle.net/hwbbukvd/
If I make that a functional component by adding functional: true, then it does not work because it's display relies on the component having state: https://jsfiddle.net/cygjdjru/
See: https://v2.vuejs.org/v2/guide/render-function.html#Functional-Components
In your case, if you aren't looking for style to be reactive, then I'm guessing you just want to pass a prop
Vue.component('message', {
functional: true,
render(createElement, context) {
return createElement('h1', context.props.foo)
},
props: {
foo: {
required: true
}
}
});
See: https://jsfiddle.net/qhzh0a2c/