Vuex: Should a component commit a mutation directly?

Created on 17 Jan 2017  ·  38Comments  ·  Source: vuejs/vuex

I open the issue here instead of the forum is because that I think this may relate to the documentation.

In the Mutations chapter, you can see an example that uses mapMutations to map mutations to component methods. It also says

You can commit mutations in components ...

So the question comes, is committing a mutation directly from a component (not through an action) fine in vuex? If it is, then this diagram may need to update:
image

also I think it is worth to mention in the docs, it's ok to commit a mutation directly from a component, because there are a lot of people come from redux, which the client only dispatches actions, won't call the reducer directly.

If this is an anti-pattern, the Mutations chapter may also need to be updated. Shouldn't we remove the mapMutations method? (but people can still use this.$store.commit('xxx'), so still need to add some clarification)

documentation

Most helpful comment

Here are just my personal opinions, I may be wrong.

I think verbose is the reason to use this kind of solution (vuex, redux.. whatever), it enforces you to follow a pattern, a flow. If you are trying to get rid of the verbosity (oh the code is so boring let's just skip that), it means that you may not need them.

And.. if a component can just mutate the store arbitrarily, we don't even need actions for synchronous cases, also because the mapMutations function, people can easily access all the mutations in a component. So the only reason to use an action is that when you want to do some async things. (Or maybe this is the pattern of vuex? Because in the todomvc example, there is no action at all..)

I think the flow of vuex is:
a component wants to do something, so it dispatches an action (action is the something, like checkout), tells the store "what happened". And the component shouldn't know how things update, and what will be updated.

An action describes the updates, it is like the controller of mutations. Mutations, are just mutations, and components are just UI.

So we separate the responsibilities clearly, when we want to update the application logic, just look into the store, never need to touch our components; if a component commits mutations directly, we may need to modify the component, also if we commit mutations everywhere, it's difficult to find out "who is committing these mutations?", it breaks the flow.

Again.. this is just my personal opinion, I know the best practice may not exist, but it is worth to add explanations of these kinds of questions in the docs, or people may confused about "why there is no action in todomvc example? doesn't the diagram say a component updates the store by dispatching actions? If components can mutate store directly then why use actions in a simple counter example?"

All 38 comments

I think it depends on your application. Committing mutations in UI is also ok in some cases.

It would be suitable for large/complex applications to always dispatch actions from UI layer. The effect of change to them would be minimized and the application would be easy to maintain as they are well separated for each concern.

But it has more boilerplate to write such code. On middle-sized application, it is painful sometimes. For example, we want to just set a value in Vuex state, it will be so verbose:

// component
export default {
  methods: {
    onInput (value) {
      this.$store.dispatch('setValue', value)
    }
  }
}

// action
actions: {
  setValue ({ commit }, value) {
    commit('setValue', value)
  }
}

// mutation
mutations: {
  setValue (state, value) {
    state.value = value
  }
}

I think it is the case we should commit mutations in component. Of course, there are also the case we should use actions in this situation (e.g. in the case you already see there will be some changes to this logic and to be complicated). It's the trade-off on designing software architecture as mentioned in docs.

Here are just my personal opinions, I may be wrong.

I think verbose is the reason to use this kind of solution (vuex, redux.. whatever), it enforces you to follow a pattern, a flow. If you are trying to get rid of the verbosity (oh the code is so boring let's just skip that), it means that you may not need them.

And.. if a component can just mutate the store arbitrarily, we don't even need actions for synchronous cases, also because the mapMutations function, people can easily access all the mutations in a component. So the only reason to use an action is that when you want to do some async things. (Or maybe this is the pattern of vuex? Because in the todomvc example, there is no action at all..)

I think the flow of vuex is:
a component wants to do something, so it dispatches an action (action is the something, like checkout), tells the store "what happened". And the component shouldn't know how things update, and what will be updated.

An action describes the updates, it is like the controller of mutations. Mutations, are just mutations, and components are just UI.

So we separate the responsibilities clearly, when we want to update the application logic, just look into the store, never need to touch our components; if a component commits mutations directly, we may need to modify the component, also if we commit mutations everywhere, it's difficult to find out "who is committing these mutations?", it breaks the flow.

Again.. this is just my personal opinion, I know the best practice may not exist, but it is worth to add explanations of these kinds of questions in the docs, or people may confused about "why there is no action in todomvc example? doesn't the diagram say a component updates the store by dispatching actions? If components can mutate store directly then why use actions in a simple counter example?"

I mean there are several approaches to make a software depends on its size etc. You can select the strategy to maximize the productivity for each case in Vuex.

I see verbosity enforce us to follow a common pattern and these kind of libraries are made for that. However, there are different of suitable verbosity level for real-world projects because they have different size, maintenance term/strategy and so forth.

I totally agree with adding explanations of this kind of discussion :slightly_smiling_face: It's definitely confusing point.

i think the mutation part is just a logger for vuex maybe we can just use actions instead?

@ralphchristianeclipse mutations allow time traveling debugging which is from my point of view the main feature of VueX. actions are optional and are mostly useful if you want to share the same asynchronous functionality between different components. Less code usually means higher speed of development, and less code to maintain. Exceptions apply if you have a team of 20 developers working concurrently on the same application. 20, not 2.

I also have a question about this. Vue support a middleware which can observe the mutation, however don't support to trace actions like redux middleware.

I think may be because the most important thing is store's change. So actions cannot change the store, so why we need the actions. Just a pattern? Just for async? Async also can use in mutation, if we change the define, like vuex-promise-middleware

I am very confused!
——————from a beginner

Sync actions are also useful as mutations controllers, as mentioned by @CodinCat. Quick example: a simple search input field. You control current icon, search term, search results to save for future usage, styling etc. If you need to control these state properties out of dirrerent components under different conditions, it's handy to organize mutations sequences into actions.

Should you use actions to avoid rapid mutations from a loop in the component locking up the UI? Pass the array of data from component to an action to loop through and commit instead?

@alexcroox the reactivity system does not notify dependents immediately upon change, instead there is a queue. Unless you forcibly drain the queue (e.g. by waiting for nextTick between commits), there should not be any noticeable difference between committing from an action and committing from a component.

The documentation shows us that the actions are only for asynchronous tasks, otherwise the mapMutations method would not exist. This is my opinion.

@viktorvillalobos I don't think it's that strict. 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.

Mutations can't do that, especially when namespaced, since they have to be pure and their only input is the state (of their module) and payload. And they shouldn't because mutations should be atomic.

Do actions always must commit mutations, can I create an action which do not commit a mutation ?

What for? Actions exist to commit mutations.

Technically, they don't have to commit a mutation, so you can (ab)use them for other purposes, tough I don't hink that' a good idea since it muddles different functionalities together.

My take is this:

Mutations are simple atomic changes to the store. Any component that wants to make changes to the store can call the appropriate changes directly. Mutations are what give Vuex its power and flexibility. Actions are never strictly necessary. Even if a component wants to make a complex, asynchronous sequence of mutations and gets, it can technically do that itself.

However, sometimes the same complex, asynchronous flow needs to be used by multiple components. Actions allow you to put that code in the store itself, so it can be used from anywhere, as easily as mutations can. Actions should be used whenever this benefit outweighs the added complexity.

I don't think it's that strict. 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.

The documentation shows us that the actions are only for asynchronous tasks, otherwise the mapMutations method would not exist. This is my opinion.
So, the documentation is somehow misleading.

Just getting into my first real project with Vuex and this question of whether components should _dispatch actions_ or _commit mutations_ has bubbled up.

A few observations:

  1. The distinction that "Actions are for asynch" didn't quite land with me. Yes, actions return promises that components can use.... but, if your component is already basing it's state on the store, you can have mutations with asych side-effects just fine, eg:
// store
  state: { results: [] },
  mutations: {
  loadClicked(state, payload) {
    loadStuff().then(results => {
      state.results = results;
    }
  }
}
// component
  computed: {
    results(){
      return this.$store.state.results;
    }
  }

(And, as a component, you'd probably rather rely on the update from the central store, rather than the action promise, generally, anyways, right?)

  1. An idea: "strict action mode". Optionally, turn this on, and Vuex throws errors if mutations are called from components. Having this feature might also clarify this decision (and trade-off) in the docs.

Thanks for reading! (And contributing to Vuex! It has been a delight to work with!)

but, if your component is already basing it's state on the store, you can have mutations with asych side-effects just fine, eg:
[...]

If you comit two mutations after one another, they should mutate the store in their order of execution. That's essential for time travelling in chrome to work correctly. having asynchronous side effects in mutations potentially breaks that, and/or re-send data to yourt backend over and over when replaying a state change in your devtools, leading to different results, like your backend returning an error because "record already exists" or something

So don't be suprised if your devtools vuex functionality don't work as expected ;)

(And, as a component, you'd probably rather rely on the update from the central store, rather than the action promise, generally, anyways, right?)

You should not return the data from an action, right - But that'S not the real usecase of the promise. It's rather often useful to simply know when the action has finished, and (re)set local loading states, notifications etc. accordingly. That won't work with mutations as they can't return anything.

Components _could_ directly call mutators, but they _shouldn't_. They should call only actions.

Actions _could_ performing only one mutation, synchronously updating a single value in the store. But they _could_ also perform multiple asynchronous operations, using many mutations, updating hundreds of store values.

Fact is, the component does not need to know.

Components should just invoke actions that sooner (synchronously) or later (asynchronously) might update the store. And although they usually respond to some of the store changes, components don't know how many action methods responded to their call (could be multiple, e.g. in different modules), how many fields in the store were changed and whether those changes were perfomed synchronously or asynchronously.

So yes, you _could_ call mutator methods directly from you components. But if you do, you are:

  • moving responsibilities (assumptions about the store implementation) to your components, making them difficult to reuse;
  • introducing extra dependencies between components and mutator methods, making both your components and your store methods difficult to maintain;
  • mixing store.commit and store.dispatch, making your code more difficult to understand.

The distinction that "Actions are for asynch" didn't quite land with me.
// store mutations: { loadClicked(state, payload) { loadStuff().then(results => { state.results = results; } } }

@peteorpeter: I added a 'thumbs down' to your comment because you should definitely not perform asynchronous operations in mutation methods. As @LinusBorg said, time traveling in Vue DevTools won't work. That's exactly what action methods are for.
Read 'Mutations Must Be Synchronous' in the Vuex documentation. It contains exactly your code with an explanation as to why that doesn't work.

Thanks for the responses @mk-relax and @LinusBorg! I'm definitely still getting a grasp of the concepts and getting clear direction helps a lot. I understand the best practices better now, and the _why_ of them.

FWIW, I don't really care about time-travel debugging (maybe that will change once I get used to it!), and as a new Vuex user and a minimalist at heart, I found myself wondering "Do I really need this?" when it came to adding Actions. On the path of understanding more about vuex, it was a moment of confusion on an otherwise clear trail, and got me googling around to find this thread.

I do think the docs, or even the library behavior, could be less wishy-washy on this topic – the opinions here seem stronger than the docs! (Which I had read a few times but _still_ thought it worth a Google.)

Personally, I think it would be nifty if Commits triggered by Components threw a warning in the console (at least when some strict mode were turned on).

(I've since added Actions and removed all Commits from Components, and I am glad to have done so. So, thanks again for the good advice!)

This issue helped me a lot as well. I am starting with Vuex and I made the same mistake: I used mutations everywhere except for the few things that require async operations that I use actions. I understood from the documentation that was the purpose.

So based upon the "Single Responsibility" principle, best practices would be:

  • (only) use actions in your UI layer (i.e. components)

Though... in the vuex documentations they do show examples with mapMutations in a component...

It is best practice to have your components dispatch actions then have your actions commit mutations.
You can directly commit mutations given that your change is not async though but this not good practice in my opinion.

@kyriediculous any reason behind that, or just your opinion?

If tou put on strict mode you can not change state outside of mutation or action handlers. Async calls can only be put in actions, to keep our Logic uniform we might as well put sync methods in actions as well.

I think the documentation needs to be more precise here or at least point out that it is up to the programmer. It does not really specify what to use when. Even though I agree with @CodinCat, it clearly says commits may be made from within any component (even offering helper functions for it) and does not lose a word about the need to stick to actions. This is too confusing.

I tend to now only dispatch actions from components, as it helps to further decouple them from the store layer.

If I make a change to my store schema and associated mutations, it's much easier to then only have to make changes to the associated actions, which will more often than not also be in the same store module.

Many good points raised above. I would add that regardless of complexity, using actions and mutations together are very helpful for error handling / contract specification within your internal API

By performing all your error checking in your actions and expecting pretty much every action you call to possibly throw an exception, it nicely separates out your application and allows you to easily specify the inputs and output.

Otherwise, you might have a simple mutation that just adds to a list. But you will probably only want to add valid items to that list. Sure you could verify the item is valid in the calling code. However, by putting the validation code in an action, you can reuse that throughout the application and delegate the validation from all client code. When this approach is followed, you get to a point where all your Vuex data sanitization is in the action layer and your application is more consistent.

I find it also encourages better design. You know that with each new action you have to specify which errors could be thrown and the data to be returned (API point of view) and then you have to handle them on the client side. And if you then come across an action with no error handling, you are immediately aware that something is likely missing.

Data (Good or bad) -> Action (Validates data and performs operations, throws error on failure) -> Mutation (Commits santized data to state)

In the near future the difference between mutations and actions will dissappear based on the vue us talk

@IMAMBAKS Which talk? Can you share with us?

https://youtu.be/TRJMT9yjONQ @30.29

It says 'potentially'

I have read an explanation from @LinusBorg about why mutations should not have access to getters, and I agree with that. Should you use a getter for mutating the state, then use an action for committing such mutation.

The thing is: I find particularly confusing to have a store as such:

mutations: {
  [SET_CLIENT](state, client) { ... }
},

actions: {
  [SET_CLIENT]({ commit, getters }, client) { /* Do something with getters in order to set a client */ }
}

That is why I am opting for using only actions in my components, with mutations being used solely by actions.

Has anyone tried to solve this "components can directly mutate the state" problem by e.g. creating a store wrapper utility that would only expose the getters and actions from the store? It probably creates more problems than it solves, but as someone coming from React+Redux and Angular+ngrx world, I feel that with Vuex it is way too easy to create an unmaintainable mess by coupling UI layer components to state management.

I want to keep my components as clean as possible and they should only do one job (and do it well) and committing mutations is not one.

It probably creates more problems than it solves, but as someone coming from React+Redux and Angular+ngrx world, I feel that with Vuex it is way too easy. ~to create an unmaintainable mess by coupling UI layer components to state management~.

My take on this is that actions are for business logic, mutations are for changing the state. Essentially, if a store (or store module) were a class, you'd have internal state, getters, setters(mutations) and functions (actions).

So my mutations are fairly simple:

addNotification: (state, payload) => {
  state.notifications.push({id: payload.id, title:payload.title});
}

While the action does validation, etc:

createNotification: (context, payload) => {
  return new Promise((resolve, reject) => {  
    if(!payload.title) {
      reject("A title must be set");
    }
    const notification = {
      id: nanoid(),
      title: payload.title
    };
    context.commit("addNoticiation", notification);
    resolve(notification.id);
  }
}

It also means that technically all mutations are private to their store module. So if I need to create a notification from another module, I'd have to dispatch an action to the Notifications module instead of committing a mutation directly.

Based on my beginner-intermediate experiences here, I _do_ call mutations directly if there is no logic involved. For example, in the vuex docs for mutation examples, they have a counter they update. So, if my component has a button to increment the count by 1, and that's all that ever happens each and every time, then forget the action, just call the mutation directly.

However, if I need to, maybe check for some upper limit or do any other type of logic (e.g. if), then we should use action.

Summarily, IDL to see any mutation that has anything like an if statement. Mutations should be very simple, maybe just a couple of lines in some cases, unless changing multiple properties at once.

We'll be closing this issue since it's a very old one, and at least for now, committing mutation inside Vue Component is really up to developers.

Terse Answer:

Use action to call mutation

If you expect a state change to be asynchronous by nature (i.e. local API call for a large chunk of data) use an action that will call the mutation that will mutate that specific part of the asynchronously and will render that part of the state whenever the asynchronous call has finished.

Mutate directly without writing an action

If you do not need to create an asynchronous call (i.e. passing local data between components)

Was this page helpful?
0 / 5 - 0 ratings