Are there any potential problems calling Vuex dispatch inside a two-way computed property?

vuex

#1

The Vuex documentation recommends a two-way computed property for using v-model on a piece of state owned by Vuex:

<input v-model="message">

computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.commit('updateMessage', value)
    }
  }
}

Elsewhere I have seen it recommended to not call commit directly from inside a Vue component, but instead to only dispatch actions from Vue components, and then to let the appropriate Vuex actions call the necessary commits. I have adopted this practice in my own code.

As such, I am instead writing my two-way computed properties in my Vue components like so:

computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.dispatch('updateMessage', value)
    }
  }
}

From my own testing, this seems to work fine, I haven’t encountered any problems so far… However, I have a bit of hesitation about this, because dispatch is asynchronous, whereas commit is synchronous.

I am wondering if anyone has any thoughts on this. Are there any potential problems with using dispatch in a two-way computed property like this, because of its asynchronous nature?


#2

Based on my experience, Certainly not, actions are used for async operations like api requests but you can do sync operations also. Where as Mutations are called wherever you want to change the state(Probably you know all this).
It is recommended because in actions we include business logic and use mutations only to change the state.


#3

Action asynchronous or not asynchronous, depending on how you use it.


#4

Ok okay, I misunderstood how dispatch works. I found this thread and that clarifies things, dispatch is only asynchronous when it returns a promsie.

Good to know that it can be used synchronously!


#5

dispatch is always a promise, regardless. If you dont return a promise, dispatch will wrap it into a promise.


#6

K thanks for the responses. Let me make sure I understand with an example. Here is a vuex action that contains only synchronous code…

// Vuex module
const actions = {
	doSomethingSynchronous() {
		// ... a bunch of synchronous code, but no asynchronous code
		console.log('a')
	}
}

// Vue component
const methods = {
	doStuff: function() {
		this.$store.dispatch('doSomethingSynchronous')
		console.log('b')
	}
}

…is it guaranteed that when doStuff is called, I will always see ‘a’ followed by ‘b’ (and never ‘b’ before ‘a’) in the console? Or to guarantee this, do I instead need to write doStuff like so:

// Vue component
const methods = {
	doStuff: function() {
		this.$store.dispatch('doSomethingSynchronous')
			.then(() => console.log('b'))
	}
}

#7

@travisb i wanna know this as well.


#8

Yes, easy to test. Look at this fiddle, you must do the 2nd example in your post, not the first example.

https://jsfiddle.net/qo30shan/

myDispatch is shown before After Dispatch console.log messages. Also its easy to confirm this by looking at the Vuex source code for Dispatch.

Which I believe here, checks the result of your handler (the function you define for your action) and whether it is a promise or not. If it is not, it wraps it with Promise using Promise.resolve.

This ensures that dispatches are always asynchornous and always return a promise.


#9

This ensures that dispatches are always asynchornous and always return a promise.

Thank you @bolerodan for clarifying this, this is useful information to keep in mind when dispatching an action alongside other code. My confusion came from comments that actions can be used synchronously – which I guess is true. But I see now that I still have to use then (and catch) blocks, to avoid surprises.

So because a dispatch always returns a promise, when an action is dispatched, that code is scheduled to be executed on the nextTick of the event loop (as I understand it). This behaviour is different from a commit, which executes synchronously.

I guess I would then come back to my original question, which is does this pose any problems for using a dispatch instead of a commit inside the setter of a two way computed property? The consensus here so far seems no, and from my own testing I haven’t noticed any problems… (Just trying to avoid the situation where I get 6 months deep into a project, only then to realize there is some kind of fringe case bug, because I opted to use dispatch instead of commit everywhere in my components.)

If you (or anyone else) has any further insight on this, it would be much appreciated.


#10

As it stands, I would recommend using commit in your set. You should only need to use an action if you’re performing an async operation. As you are simply setting a value, it makes no sense to use an action. Perhaps so far your use cases have been validated (using an action as a synchronous method), but for future developers on the project this may not be so apparent. They may use an async action within a set without understanding the effects of this.

For this alone (and as you said, the potential of running into some issue 6 months in) I would stick with synchronous commits in my computed setter.


#11

Thanks James, that makes sense, that sounds like a sensible approach. That is how I will proceed.

I appreciate the input from all of you. I am quite new to Vuex (and Vue) and there are a lot of new concepts to absorb – it isn’t immediately obvious when one would choose to use a dispatch vs a commit – so thanks for being patient with me! I feel like I am starting to get a better sense of this now.

For anyone else who, like me, is struggling to wrap their head around this stuff, I found the discussion here to also be helpful. In particular, this statement:

To me, actions are responsible for orchestrating changes to the store that require async operations and/or mutlipple mutations to different parts of the store.


#12

Yep. That sums it up nicely.