Change data value with reactivity in vue - vue.js

I'm trying to reduce the value of a data property in every 1 second as follows:
data() {
return {
timer: null
}
},
mounted() {
this.timer = 50;
window.setInterval(() => {
this.$set(this, 'timer', this.timer - 1)
}, 1000);
},
https://jsfiddle.net/eywraw8t/8179/
In the Vue devTool, the timer is not automatically updated. However, in the jsfiddle, the output is getting updated. Is this reactive? If not, how can I make it reactive?

As #connexo already mentioned in the comment section timer is indeed reactive. This is why it changes its value in the template.
The only reason for this I could think of is that you don't include timer in the component template in your environment. There's a related Github issue describing that so far if there's no DOM elements affected by data, vue-devtools won't be updated either.

Related

Vuejs setting the data is not reactive

am completely new to Vuejs, sorry if this is a stupid question.
This is a nuxt app and am using an IntersectionObserver and depends on the element visibility am trying to change the internal state (data). but its not reactive unless i hit the refresh in vue dev tools.
so this is my approach
async mounted(){
let options = {
root: document.querySelector('#scroll-root'),
rootMargin: '0px',
threshold: 1.0
}
const testimonialStart:any = document.querySelector('#testimonial-start')
let startObserver = new IntersectionObserver(((entries,observer)=>{
entries.forEach((entry)=>{
if(entry.isIntersecting){
this.updateTesti(false)
}
else{
this.updateTesti(true)
}
})
}), options);
startObserver.observe(testimonialStart)
}
in the methods
updateTesti(st:boolean){
this.testiPrev = st
console.log(st,'state')
},
in the data
data(){
return {
testiPrev:false,
}
}
there is no issues in the intersection observer, in the console.log am getting the boolean value as expected.
what should i need to do to get reactivity here?
temporary solution:
I found that if I add testiPrev like below inside watch am getting the reactivity.
watch: {
testiPrev: function(){
}
},
this made me to ask one more question, do we need to explicitly include all the properties inside watch to achieve reactivity, if any better way please let me know.

How to call a method after Vuex data is available?

I would like to call a method once, as soon as possible after its component loads, but it needs to be after a computed property that gets data from Vuex is defined.
For example:
computed: {
my_data: function() {
return this.$store.state.my_data;
}
},
methods: {
useData: function(){
axios.post('api/fetch', {'data': this.my_data});
}
},
mounted() {
this.useData(); //error: this.my_data is undefined;
},
watch: {
my_data: function(){
this.useData(); //never triggers
}
}
If I call this.useData() from mounted, my_data is still undefined. I tried setting a watcher on my_data, but it never triggers. I feel like I'm missing something obvious here.
Make sure the data in my_data is updating correctly in store. If still have issue, then use deep to watch my_data
watch:{
my_data:{
handler:function(){
this.userData();
},
deep:true
}
}
If you're using watch to trigger method, don't need to use to call it from the mounted.
It turns out the "undefined" error was caused by another object that shared a key name with my stored object. Unfortunately, the nebulous error message sent me on a wild goose chase after I assumed the stored object was the issue based on my inexperience with Vuex.

Vue watcher's first callback not ordered "correctly" on post-instantiation watchers

I have the following doubt, regardless of the quality of the code or the best practices not followed.
I have a Vue component with two watchers. One of them is declared on the component's watch object (Therefore instantiated at its creation), and the other one is instantiated at the mounted(), in order to avoid calling it on the first mutation.
data: {
return() {
watchedAtInit: null,
watchedAfterMount: null,
};
},
mounted() {
api.fetchSomething().then((response) => {
this.watchedAtInit = response.watchedAtInit;
this.watchedAfterMount = response.watchedAfterMount;
this.$watch('watchedAfterMount', this.watchedAfterMountMethod);
});
},
watch: {
watchedAtInit() {
// something
}
}
methods: {
watchedAfterMountMethod() {
this.watchedAtInit = someValue;
},
}
The problem here is at the end: The watched variable watchedAtInit is being mutated from another watcher watchedAfterMountMethod, which I understand is not good practice.
Still, something strange happens when mutating watchedAfterMount:
The first time I mutate watchedAfterMount, the watchedAtInit() watcher is called before the watchedAfterMountMethod(), despite the mutation of watchedAtInit occurs at the watchedAfterMountMethod.
In further watchedAfterMount mutations, the order is indeed as expected: the watchedAfterMountMethod is called, which mutates watchedAtInit, therefore triggering its watcher.
¿Why is the watchedAfterMount watcher delaying at its first mutation? ¿Does it have something to do with Vue's reactivity system, or is it merely the result of an incorrect implementation of watchers?
¿Could I be missing anything else?
Thanks in advance.
Watchers won't be intialized on mounted by default, but you can force this behaviour writing your watchers like this:
watch: {
watchedAtInit: {
immediate: true,
handler() {
// something
}
}
}

You may have an infinite update loop in a component render function error - solution?

I am getting error "You may have an infinite update loop in a component render function." What should I do?
I have tried making the arrays a data value. Also, I have tried using a for loop. It seems like it's isolated in the first method.
data() {
return {
activeTab: 0,
uniqueLobs: []
}
},
methods: {
addDollarSymbol(val){
var newVal = "$" + val;
return newVal.replace(/<(?:.|\n)*?>/gm, ''); // Trims white space
},
removeDuplicateLOB(lineOfBusiness) {
// Removes duplicate LOBs for tabs
let incomingLobs = [];
lineOfBusiness.forEach((business) => {
incomingLobs.push(business.line_of_business.name);
});
this.uniqueLobs = [...new Set(incomingLobs)];
return this.uniqueLobs;
},
showSpecificLobData(activeTab){
//compares tab LOB to all incoming card data and shows only the LOB data for that specific tab
let activeTabData = [];
this.product_rate_card.forEach((product) => {
if (product.line_of_business.name == this.uniqueLobs[activeTab] ) {
activeTabData.push(product);
}
});
return activeTabData;
}
}
The 'loop' in this case refers to an infinite recursion rather than a for loop.
That warning is logged here:
https://github.com/vuejs/vue/blob/ff911c9ffef16c591b25df05cb2322ee737d13e0/src/core/observer/scheduler.js#L104
It may not be immediately obvious what most of that is doing but the key part of the code is the line if (circular[id] > MAX_UPDATE_COUNT) {, which is checking whether a particular watcher has been triggered more than 100 times.
When reactive data changes it will cause any components that depend on that data to be re-rendered. If the rendering process changes that same data then rendering will be triggered again. If the data never stabilizes then this will continue forever.
Here's a simple example of a component that triggers that warning:
<template>
<div>
{{ getNextCount() }}
</div>
</template>
<script>
export default {
data () {
return {
count: 1
}
},
methods: {
getNextCount () {
this.count++
return this.count
}
}
}
</script>
The template has a dependency on count but by calling getNextCount it will also change the value of count. When that value changes the component will be re-added to the rendering queue because a dependency has changed. It can never break out of this cycle because the value keeps changing.
I can't say for sure what is causing this problem in your component as you haven't posted enough code. However, it could be something like the line this.uniqueLobs = ..., assuming that is being called during rendering. In general I would suggest avoiding changing anything on this during the rendering phase. Rendering should be read-only. Generally you'd use computed properties for any derived data that you want to keep around.
Most times it’s as a result of how you're passing props to another component.
If it’s Vue.js 2, try using v-on:[variable-name].

Reactivity trigger for Watching computed properties in Vue

Normally when working with Vue, I expect the callback for a watched property to be triggered only when the value of that property changes. However, a colleague noticed that this does not seem to hold when watching computed properties, as can be demonstrated by the following example:
<div id = "demo">
{{ numbers }} </br>
{{ evenNumbers }}
</div>
<script src="./vue.js"></script>
<script>
var demo = new Vue({
el: '#demo',
data: function(){
return {
numbers: [1,2,3,4,5,6]
};
},
computed: {
evenNumbers: function () {
return this.numbers.filter(x => (x % 2 == 0))
}
},
watch: {
evenNumbers: function (val) {
alert("yes, computed property changed")
}
}
})
setTimeout(() => { demo.numbers.push(7) }, 5000)
</script>
After 5s, the alert is displayed, but the value of the computed numbers array doesn't change. It's tempting to infer that the watcher is triggered if the dependencies of the computed property update, even when the computed property itself doesn't.
It turns out that this suits us fine for the application we're working on, but I don't understand the behaviour, and I don't know if we can rely on it, or under what conditions it will hold. (For example, I have two arrays here, but would it still work if I had primitives involved instead? I have no idea, and I might experiment if I have time, but issues with comparing object equality were just the first thing that occurred to me as I typed this, and the pitfalls with Vue's reactivity and composite objects were the second.) I'd also imagine it might be an unpleasant surprise if the callback to your watcher were an expensive operation.
If anyone could explain how this works, and if we can rely on this behaviour, I'd be grateful.
Every time evenNumbers() is executed, it generates an entirely new array. Since arrays are compared by equality of reference, they can never be equal. The only way to properly detect this change is to manually compare the contents of the previously calculated array to the newly calculated one.
Example, using lodash:
import { isEqual } from 'lodash';
...
watch: {
evenNumbers(newValue, oldValue) {
if(!isEqual(newValue, oldValue) {
alert('callback')
}
}
}
The watcher was triggered because it had no way of knowing whether the change on data.numbers will affect the result of computed.evenNumbers.
However, upon recalculating, it discovers that 7 is not even, so the array remains [2, 4, 6].
If you want to make sure the callback only runs when the value actually changes, you can designate it like
watch: {
evenNumbers(newValue, oldValue){
if(newValue !== oldValue) {
alert('callback')
}
}
}