0
votes

Except that a mutation must be synchronous whereas an action can contain asynchronous code, I find it difficult to establish some guidelines on how to correctly choose where the logic is supposed to be implemented.

The Vuex doc describes the mutations as transactions, which makes me think that each mutation must ensure as much as possible state coherence, potentially doing multiple changes in the store within the same mutation.

Let’s take for example the following state, storing documents that can either be loading or loaded:

const state = {
  loading: []
  loaded: []
}

const mutations = {
  setLoaded(state, doc) {
    state.loaded.push(doc)
    // Remove doc from loading:
    const index = state.loading.find(loadingDoc => doc.id === loadingDoc.id)
    if (index !== -1) {
      state.loading.splice(index, 1)
    }
  }
}

Here the mutation ensures that the doc is either in loading or loaded state, not both. The problem doing so is that the mutations could eventually end up having a lot of logic, which could make them more difficult to debug through the Vuex dev tools.

Some prefer to keep the mutations as simple as possible (eg. only one state change per mutation), but then it’s up to the actions (or the components in case they directly call mutations) to ensure state coherence, which could be more error-prone since, in the above example, you would need to remove the doc from loading state each time you call the “setLoaded” mutation.

As for data validation, unluckily we have no simple way to do it in mutations since mutations cannot return anything. So an action calling a mutation has no way to know that the data is invalid. Eg.:

const mutations = {
  setValue(state, intValue) {
    if (intValue < 0) {
      // error
      return
    }
    state.value = intValue;
  }
}

Thus, it seems that data validation needs to be done in actions, with mutations assuming that the data they receive are correct.

What are your thoughts about this, and more generally about actions and mutations? Do you have guidelines on how to properly use them from your own experience?

1

1 Answers

0
votes

Data validation MUST be done in actions if it encompasses multiple mutations and/or needs to signal to the caller if the operation failed.

My rule (and this will be argued...) is to always use async CALLS (with async/await) whenever possible, even if I am calling sync methods. Then my code is future proof if the sync method becomes async. And in the case you mentioned, it also allows a return value and/or error from Vuex.

let response
response = await anAsyncMethod()
response = await aSyncMethod()
response = await aVuexMutateOp()
response = await aVueActionOp()

Yes this adds a bit of processing overhead, but it is also just as clear by using async/await syntax. It streamlines the error processing as well, routing it to a catch block.

Because mutations have the restriction that they MUST be synchronous AND CANNOT call other mutations AND DO NOT return a value demonstrates that the mutate should only be used in the simplest of situations.