Code for watched computed property never seems to run - vue.js

I'm watching a computed property returning a state variable.
I know for sure that this state variable changes as a result of certain things I do. Yet the console.log() code in my watch: {} never executes:
computed: {
simulation () {
return this.$store.state.simulation
}
},
watch: {
simulation () {
console.log('simulation changed:')
}
}
What am I doing wrong?

If your this.$store.state.simulation is object, you can't watch for change property of object. You can watch change of object (when this.$store.state.simulation = newVal, but not this.$store.state.simulation.anyProp = newVal)
you should use deep watch (https://v2.vuejs.org/v2/api/#watch)

Through our conversation in the comments of the original post, we found that a property of simulation was being changed, rather than the entire simulation object. To detect such changes, OP simply needs to watch the specific property of simulation that is being changed, rather than the whole simulation object.

Related

How could i get event type in vue when watch listener triggered?

I have created a wacther and when <Input v-model="computedData" /> changes the data, I can get the old and new values. Data can also be changed via XMLHttpRequest. I need to know who changed the data. I can't get event as parameter via watcher when data changes. Because there is no argument to get to event on whatcher. I know, I can access the event directly using event. But I also know it's deprecated. So I'm researching how the event type can be accessed as InputEvent nor XMLHttpRequest.
#Options({
name: 'dx-table',
watch: {
computedData: {
handler(newData: any, oldData: any) {
console.log(event); // is there any way to access `event` without using `event` directly
},
deep: true,
immediate: true,
},
},
})
export default class DxTable extends Vue.with(Props) {}
There is no way to get the event or cause of the data change in a watcher. A watcher is simply a function that executes whenever some reactive property changes and all you are given is the old value and the new value.
Based on the information given, there's two ways the data can be changed:
input event: Register a listener for the input event on the component, like #input="handleInput". The event object is passed to the function.
XMLHttpRequest: Wherever you are changing the property in code, just call a method to handle that specific mutation.
I don't know specifics about your code, but this might be a situation where instead of mutating the data freely throughout your code, use one or more "setter" methods to do this so that you know exactly where and how the data is being mutated. Using a watcher gives you no information about where or how the data changed, and if you're mutating the data in many random places in your codebase then you're going to have a difficult time trying to trace through your code to find the cause of the mutation.

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
}
}
}

How do I make a state getter reactive when a dispatched action sets a state object with Vue.set?

I have a button that’s set to update the a store object using Vue.set but the getter for that same piece of data in a different component isn’t reactive until I change the state using a different component method.
The state object in question is set up as a hash that's keyed by UUID's. The object is generated and then added to the state object with Vue.set
The button is set to dispatch an action, which I see it going through immedietely in the devtool, that does this:
mutations: {
COMPLETE_STEP(state, uuid) {
let chat = state.chatStates[uuid];
let step = chat.currentStep;
Vue.set(chat.data[step], "complete", true);
}
},
actions: {
completeStep({ commit }, uuid) {
commit("COMPLETE_STEP", uuid);
}
},
Now, when I want to grab that data, I have a getter that grabs that data. This doesn't run until I do something else that causes a re-render:
getters: {
getChatStepComplete: state => (uuid, step) => {
let chatState = state.chatStates[uuid];
return chatState.data[step].complete;
},
}
I want the getter to show the updated change right away instead of waiting to update on a different re-render. How do I make that happen?
Figured out my issue: I wasn’t creating the data array when I first create and add chat to the state. Once I started initializing it to an empty array, it’s started being reactive.

Vuejs Watch jumping one tick

I'm developing an app using Vuejs and Vuex.
I've got a Vuex module called settings_operations. This module has the following action:
async changePassword ({ commit }, { password, id }) {
commit(CHANGE_PASSWORD_PROCESSING, { id })
const user = auth.currentUser
const [changePasswordError, changePasswordSuccess] = await to(user.updatePassword(password))
if (changePasswordError) {
commit(CHANGE_PASSWORD_ERROR, { id, error: changePasswordError })
} else {
commit(CHANGE_PASSWORD_SUCCESS, changePasswordSuccess)
}
}
Edit: the to() is https://github.com/scopsy/await-to-js
With the following mutations:
[CHANGE_PASSWORD_PROCESSING] (state, { id }) {
state.push({
id,
status: 'processing'
})
},
[CHANGE_PASSWORD_ERROR] (state, { id, error }) {
state.push({
id,
error,
status: 'error'
})
}
And then, in the component I want to use this state slice:
computed: {
...mapState({
settings_operations: state => state.settings_operations
})
},
watch: {
settings_operations: {
handler (newSettings, oldSettings) {
console.log(newSettings)
},
deep: false
}
}
The problem is that when the changePassword action results in an error, the watch doesn't stop in the PROCESSING step, it goes directly to the ERROR moment so the array will be filled with 2 objects. It literally jumps the "processing" watching step.
A funny thing that happens is that if I add a setTimeout just like this:
async changePassword ({ commit }, { password, id }) {
commit(CHANGE_PASSWORD_PROCESSING, { id })
setTimeout(async () => {
const user = auth.currentUser
const [changePasswordError, changePasswordSuccess] = await to(user.updatePassword(password))
if (changePasswordError) {
commit(CHANGE_PASSWORD_ERROR, { id, error: changePasswordError })
} else {
commit(CHANGE_PASSWORD_SUCCESS, changePasswordSuccess)
}
}, 500)
},
It works! The watch stops two times: the first tick displaying the array with the processing object and the second tick displaying the array with 2 objects; the processing one and the error one.
What am I missing here?
Edit:
I reproduced the problem here: https://codesandbox.io/s/m40jz26npp
This was the response given in Vue forums by a core team member:
Watchers are not run every time the underlying data changes. They are only run once on the next Tick if their watched data changed at least once.
your rejected Promise in the try block is only a microtask, it doesn’t
push execution to the next call stack (on which the watchers would be
run), so the error handling happens before the watchers are run.
additionally, when you mutat an object or array, the newValue and
oldValue in a deep watcher will be the same. See the docs:
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.
and as a final sidenote, I’ve never seen anyone use an aray as the
root state of a module, I have no idea if that will work for vuex in
all possible circumstances. I certainly would not recommend doing
this.
Edit with a better and more complete answer from the same member:
Why watchers are asynchronous at all? Because in the vast majority of
use cases, watchers only need to react to the last synchrnous change
that was done. In most cases (in the context of a component), it would
be couterproductive to to react to every change since you would
re-trigger the same behaviour mutliple times even though in the end,
only the last state is the important one.
In other words: Running a watcher on each change by default would
probably lead to apps that burn a lot of CPU cycles doing useless
work. So watchers are implemented with an asynchronous queue that is
only flushed on nexTick. And we don’t allow duplicate watchers then
because the older instance of a watcher would apply to data that
doesn’t “exist” anymore in that state once the queue is flushed.
An important note would be that this only applies to synchronous
changes or those done within a microtask, i.e. in an immediatly
resolving or failing promise - it would, for example, not happen with
an ajax request.
Why are they implemented in a way that they are still not run after a
microtask (i.e. an immediatly resolved promise? That’s a bit more
coplicated to explain and requires a bit of history.
Originally, in Vue 2.0, Vue.nextTick was implemented as a microtask
itself, and the watcher queue is flushed on nextTick. That meant that
back then, a watcher watching a piece of data that was changed two
times, with a microtask (like a promise) in between, would indeed run
two times.
Then, around 2.4 I think, we discovered a problem with this
implementation and switched Vue.nextTick to a macroTask instead. under
this behaviour, both data chhanged would happen on the current call
stack’s microtaks queue, while the watcher queue would be flushed at
th beginning of the next call stack, wich means it will only run once.
We found a couple of new problems with this implementation that are
much more common than the original issue with microtasks, so we will
likely switch back to the microtask implementation in 2.6. Ugly, but
necessary.
So, this should do the trick for now:
await Vue.nextTick();

How can I watch synchronously a state change in vuex?

I am using an opensource vuejs + vuex project and this is the source https://github.com/misterGF/CoPilot/tree/master/src/components
I am currently having problems knowing how to trigger an event from one components to another.
I can use this.$state.store.commit("foo", "bar") to store information in vuex, but when two seperate have two difference export default {} I don't know how I can make the app aware whenever "foo" is for exampled changed to "baz" ... unless I refresh/reload the app, there is no way for me to know the changes
Use this.$store.watch on your component. Created() is a good place to put it. You pass it the state to watch and then specify the callback for when it changes. The Vuex docs do not give good examples. Find more information on this Vuejs Forum page. Store watch functions the same as the vm.$watch, so you can read more about that here in the Vue docs.
this.$store.watch(
(state)=>{
return this.$store.state.VALUE_TO_WATCH // could also put a Getter here
},
(newValue, oldValue)=>{
//something changed do something
console.log(oldValue)
console.log(newValue)
},
//Optional Deep if you need it
{
deep:true
}
)
Your question is not entirely clear so I am going to make some assumptions here.
If you simply want your app to know when a store value has changed you can use a watcher to watch a computed property that is directly linked to a store getter:
So you would set a watcher on something like this:
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
},
watch:{
doneTodosCount(value) {
console.log(`My store value for 'doneTodosCount' changed to ${value}`);
}
}
If you want your commit to behave differently depending on what the current value of your foo property is set to, then you can simply check for this in your commit function.
Let me know if you have some more questions.