DEV Community

Cover image for Watch for Vuex State changes!

Watch for Vuex State changes!

Vinicius Kiatkoski Neves on January 07, 2019

This is my first post on Dev.to so I would appreciate any feedback which could help me to improve my overall writing and also things that I might h...
Collapse
 
adamb_11 profile image
Adam Beguelin

Great post, thanks for taking the time to write it.

I'm trying to watch a Vuex user object and then do a Vuefire query when it gets populated.

I tried your suggestion but the watcher never fires. If I add the immediate flag, it will fire, but then the object I get doesn't seem to be the user object that I'm watching. Weird, right? (Not sure how to tell what object I'm getting, but it doesn't have any of the user fields like id or display name...)

gist.github.com/adamb/6b52d7127b39...

I this example the watcher is called but the object doesn't seem to be the user, because there is no user.id.

Also, the Vuex watcher is never called, but when the beforeDestroy() is called I get an error:

[Vue warn]: Property or method "handles" 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.

I looked at the docs and I am declaring handles[] so I'm not sure why it's complaining.

Any suggestions on what to try here?

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey Adam, I'm glad that you liked it!

Let me try to help you from your gist.

You've a computed property called user which comes from your store, right? You don't watch computed properties (vuejs.org/v2/guide/computed.html). The computed property will have a set/get methods that you can customize for our needs. So you know when it changes (set is called).

If you want to watch, you can still do watch the getters.user so whenever the user changes (I'm assuming that is what you want here) this watcher will be called. I've tried to copy from your example the code below and adapt it but please double check the calls.

this.unwatch = this.$store.watch(
  (state, getters) => getters.user,
  (newUser, oldUser) => {
    this.$bind('handles', firebase.firestore().collection('handles'))
  },
)

What is happening here is:

  • You watch getters.user so I assume you've some user state that gets committed and a getter that maps to this state
  • Whenever the getters.user changes (new entry/property - watch out for Vue.set in this case) the watcher is called (where you want to call firestore)

I hope it is what you want (as I don't know in details it gets a bit tricky to fully understand the example). Let me know if I helps or let me know if anything else is needed.

Collapse
 
adamb_11 profile image
Adam Beguelin

Yes, you're correct. user is in the store and I'm trying to get data from Firestore when user changes. But this.$store.watch never gets invoked. If I console.log it in mounted it's null. But when I console.log it in beforeUpdate it's set. Yet, this.$store.watch is never called.

If I use the normal watcher on user it gets invoked, but the values don't seem to be there. I can log them and they are undefined. Perhaps this is because user is a Vuex computed property. I tried deep: true but then the (non-store) watcher is invoked twice, neither time with the data I need.

Any idea why the watcher isn't being called even through user is obviously changing?

Thanks for your help!

Thread Thread
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey Adam, to help our discussion I've created this Codesandbox: codesandbox.io/s/agitated-haze-ojdun

Please check store.js and App.vue. I've tried to create a fetch logic (full user and just username). Please mind the comments and play with comment/uncomment the deep and immediate options.
Also mind the first warning here vuejs.org/v2/api/#vm-watch once you hit the Fetch new username button.

Collapse
 
allanjeremy profile image
Allan N Jeremy

This saved me a tonne! Using it to fetch a user's id from the db (which may not be present at start). The user id is then used to determine which chats to show.

Before this, I kept getting null exceptions. Now I can just check for whether a value exists on change and act accordingly.

Even better, 0 errors now.
Thanks a lot Vin 👌

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Great! Really happy to "hear" 😅 that!

Collapse
 
fidoogle profile image
Fidel Guajardo

Very nicely done. I used your information to automatically populate a Bank Name input field whenever a user enters a valid transit routing number (TR) in another input field. The TR is sent to a validation service on the server, which returns a JSON of the bank found, or an error. I watch for a "valid" routing number and populate the Bank Name from the "name" found in the JSON. If the routing is "invalid" then I ignore it and do nothing to the Bank Name.

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey Fidel, thank you very much!

Really nice use case, thanks for sharing!

Was it easy to setup or did you find any problems in the way? Did you also find something else I could add to this post as a resource maybe?

Collapse
 
maprangsoft profile image
Maprangsoft

thank you very much from thailand.

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

You are welcome, from Brazil 😅

Collapse
 
daviprm profile image
Davi Mello • Edited

Amazing!! ty sm 🧡

Collapse
 
arik14 profile image
Faisal Arisandi • Edited

Hello, may I ask you something.
In above example, you were assigning return function from this.$store.subscribe into this.unsubscribe, also return function from this.$store.watch into this.unwatch.

But you never declare unwatch and unsubscribe in the data property.
That would mean unwatch and unsubscribe is added directly into local "this" in this Vue component.
Is it good practice?

Or should I add something like this,

data() {
  return {
    unwatch: null,
    unsubscribe: null
  }
},
methods: {
    ...
}
created() {
    ...
}
etc.
Enter fullscreen mode Exit fullscreen mode
Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey!

The difference is basically that I'm not watching these values so I don't need to declare them in the data property. The downside, of my approach, is lack of visibility (you've no clue, without reading the code, that these values exist). It is definitely better now if you use the Composition API.

I hope it helps, let me know if you still have questions!

Collapse
 
rolandcsibrei profile image
Roland Csibrei

IMHO there is a very important thing missing. If you want to watch for a FIELD change in an object or an item change in an array, you need to add deep:true to the watch method and use Vue.set to modify the watched object/array/field.

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey Roland, thanks for your feedback.

That is true. I will update the article and add it (with links to the original docs about this corner case).

Nevertheless I strongly suggest to use Vue.set whenever possible to avoid pitfalls :)

Collapse
 
theallenc profile image
AC1556 • Edited

Easy to understand and cater the solution to match my specific situation. One note though: I'm using Vuex as a substitute for event emitters and since my state values might not always be changing I had to use the vuex subscribe as watchers only respond to changes.

On that, it seems that Vuex can be really useful when trying to extend the functionality of event emitters; is this a recommended practice?

For example, I'm creating a simple showcase of UI elements and I have a form. The elements that make up the form would essentially be firing off events when they are clicked/modified/etc. As far as I can tell, using event emitters, the only way to respond to these events is to use v-on:<event name> on all of the parent elements. Given this:

That would mean putting @:btnClick="<fxn>" on every element. That just seems inefficient. This use-case is not ideal, but given an application that has a lot of large forms responding to events on every parent and then propagating it up seems like it could be a mess.

TLDR:

If I have a generic button component that I want to fire off a function that is held in a specific vue page, would using the button to modify the application vuex state be a good practice?

Thanks!

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey @allen1556 , first of all thanks for the kind feedback!

Well, I will try my best to understand your issue and help you.

I think you're concern about adding some logic to your button, am I right? From what I understood you want to keep it "pure", which means that is just emits an event and the parent handles it the way it wants but as you've just shown, you might have 10x this button and have to add the props (which are different for each button) + the same handler over and over, right?

If I got it right, adding the handler to your button component might not allow you to use in other places. What you can do is to have a wrapper that handles this event (the wrapper forwards everything down to the component). The @click would just dispatch your action for example. So, short answer, yes, it is fine to update your state like that. The wrapping component would have this "store" logic and would make sense just on this part of your application and your button would still work anywhere else you need.

Did I get your idea right?

If not, please, let me try it again 😅

Collapse
 
typerory profile image
Rory

Thank you. I'm monitoring a total count of video cameras in a reactive 'smart' shopping cart. If that number is greater than 4 then vuex queries the db for the model number of our recommended 8-channel NVR. I'm placing that model number in the state.

Now with your solution, I can just watch the state and replace the NVR in the cart with whatever model number is in the state.

Thank you. Thank you. Thank you.

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Super cool man! I think it is very unique use case this one!

Collapse
 
opolancoh profile image
Oscar Polanco

Do I need to unsubscribe the mutation in the destroyed hook?

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Very good question Oscar.

Yes, you do in case your component gets destroyed and the watcher is not needed anymore. You can read about it here: vuex.vuejs.org/api/#subscribe

I will update the examples and the post to add this information. Thanks for asking!

I'm just curious, could you share your use case? I can imagine something like navigating to a new route where the watcher doesn't make sense anymore but can't think about a lot of other use cases =[

Collapse
 
opolancoh profile image
Oscar Polanco • Edited

Hi Vinicius,

It's just to try to undesrstand the Observable pattern. When you unsubscribe, you cancel Observable executions and release resources. If you don't do it, you'll probably avoid memory leaks. I don't know if Vuex does that for you.

Thread Thread
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey Oscar,

Exactly. Once you subscribe you have to unsubscribe unless your Subject has some kind of check to remove the Observer in case it doesn't exist anymore.
For Vuex you've to do it manually, which means you've to unsubscribe once your component is destroyed.
If you don't do it you might end up in trouble as you mentioned =]

I didn't get if you were asking or not but I'm just confirming hehehe

In case you've any other question, let me know =]

Thread Thread
 
opolancoh profile image
Oscar Polanco

Thanks!!

Collapse
 
ramblingsofben profile image
Ben Wain

Thanks for this, learned a lot. I was wondering if there was a way when using vuex watch to reset the status to 'pending' by a user action in the component?

For example if the user submits incorrect details and gets the error, then clicks back into the form that it would then reset the status back to pending?

Thanks

Collapse
 
xgenvn profile image
Brian Ng

Hmm, the usage of 'mounted' hook to fetch API seems to be contrast from what I have read recently.

Are you sure that is the correct place to put API call?

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey!

Sorry for the late reply, I was on vacation for a week and attending a conference this week!

Yeah, I definitely agree that you might use it in the created hook, I use it like that most of the time. Nevertheless regarding the examples shown above it won’t have an impact in the logic itself. The big difference is if you want to access the DOM or not, or if you have SSR in place as well which can cause some inconsistencies as created hook runs in the server and mounted doesn’t.

Do you think it would add any value updating the examples?

Thanks!

Collapse
 
xgenvn profile image
Brian Ng

From my previous read, I would love to see we can provide consistent tutorial and samples.
I also use mounted hook to fetch API due to non-SSR requirements. But then I'm working on some Nuxt projects so I need to change my own practice.
If you can update the examples then it'll be perfect. Thank you.

Thread Thread
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

It took a bit longer than what I expected but just updated the repository with the examples. I'm going to update the examples here as well. Thank you again for pointing it out!

Thread Thread
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

It should be all up-to-date now. I've also checked the text itself to update any references to mounted.

Collapse
 
himito profile image
Jaime Arias Almeida

Thank you a lot for your post. It's very useful !

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Nice you liked it!

If you don't mind, could you describe where you've used (or plan to use) this strategy?

Collapse
 
himito profile image
Jaime Arias Almeida

Of course. I'm writing a tool for designing formal models, and I needed a way to display warning messages from my components (e.g, after login/registration etc.).

Thread Thread
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Cool! Got your idea!

Thanks for sharing :D

Collapse
 
yassineche profile image
Yassine Cheddadi

Hello,

I'm really grateful for this article
But using "computed: mapState(['status'])", denied you to put another computed func,

How did you solve this problem ?

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey Cheddadi,

If I understood you correctly you want to have this mapState + another computed property, right?

If so, you can do it like that:

computed: {
  ...mapState(['status']),
  myComputedProperty() {}
}

You can read more about it here: vuex.vuejs.org/guide/state.html#ob...

Hope it helps! Have a nice day!

Collapse
 
yassineche profile image
Yassine Cheddadi

Hey Neves,

Yes, its work fine now.

I'm just wondering about these three dot (...) !! What they do?

Thread Thread
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Nice to hear that!

It is called Destructuring assignment. You can read more about it here: developer.mozilla.org/en-US/docs/W...
In short (from MDN)

The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

Collapse
 
rolandcsibrei profile image
Roland Csibrei

+1 for the subscribe method! Thx!

Collapse
 
violetboralee profile image
Violet.Lee

Many thanks for your article <3 I've refactored my dirty code with using the info here :)

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey! Glad it helped!
Could you share your use case and why do you consider your code dirty?
Thanks!

Collapse
 
crazyoptimist profile image
crazyoptimist

Thanks!
snipped -> snippet (you could fix this typo)

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

🙈 thanks! I will fix it =]

Collapse
 
isabolic99 profile image
isabolic99

Or you could set getter as a function call, example:


isSearch: (state: CustomsState) => () => {
return state.isSearch
},

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey @isabolic99 , sorry but I didn't get your point here. Could you add more context?

Thanks!

Collapse
 
rolandcsibrei profile image
Roland Csibrei

Thanks!

Collapse
 
tsikah profile image
tsikah

Very helpful, been looking for this for some time now. Thank you very much

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Thanks! Could you share your use case?

Collapse
 
javier123454321 profile image
Javier Gonzalez

I have t say, I keep comming back to this article, great job writing this

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Thank you very much!

I really appreciate the feedback!

If you have any tips on how to improve it (more examples, use cases, code rewriting...) I would love to hear!

Collapse
 
tazim404 profile image
Tazim Rahbar

I want to ask that why my vuex store state chnage when i try to chnage the data using the method in my component.

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey @tazim404 could you elaborate more your question? Which method are you talking about? Could you maybe share some code?

Thanks!

Collapse
 
eladc profile image
Elad Cohen

Why you use Vue.set(state, 'status', status) instead of just state.status = status?

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

In this case it is not necessary and I could have use state.status = status, no worries.

But as I'm more concerned with (vuejs.org/v2/guide/reactivity.html) I've got used to always write Vue.set to avoid any pitfalls while developing. So it is just a way to keep the codebase concise (in one mutation it is state.prop = prop and in the other is Vue.set...).

Collapse
 
williamvittso profile image
William Vit Tso

Thanks. Useful

Collapse
 
revel109 profile image
Abu Yusuf

I got vuex store undefined when my application redirected from PayPal sandbox. The store got undefined and the auth middleware doesn't apply as expected.

Don't know how to fix this?

Collapse
 
viniciuskneves profile image
Vinicius Kiatkoski Neves

Hey Abu,

Could you give some extra input here? I didn't fully get what you mean. Could you maybe share a gist where I can try to understand your situation a bit better?

Thanks!