119

I have a vuex store, like following:

import spreeApi from '../../gateways/spree-api'
// initial state
const state = {
  products: [],
  categories: []
}

// mutations
const mutations = {
 SET_PRODUCTS: (state, response) => {
   state.products = response.data.products
   commit('SET_CATEGORIES')
 },
 SET_CATEGORIES: (state) => {
   state.categories = state.products.map(function(product) { return product.category})
 }

}

const actions = {
 FETCH_PRODUCTS: (state, filters) => {
   return spreeApi.get('products').then(response => state.commit('SET_PRODUCTS', response))
 }
}

export default {
  state,
  mutations,
  actions
}

I want to call mutation: SET_CATEGORIES from mutation: SET_PRODUCTS, But this gives me error:

projectFilter.js:22 Uncaught (in promise) ReferenceError: commit is not defined(…)

What should be correct way to do this. I tried store.commit and this.commit, but these also gave similar errors.

Saurabh
  • 71,488
  • 40
  • 181
  • 244

16 Answers16

155

If you absolutely must commit two mutations, why not do it from an action? Actions don't have to perform async operations. You can destructure the commit method in your action the same way you do with state like so:

commitTwoThings: ({commit}, payload) => {
  commit('MUTATION_1', payload.thing)
  commit('MUTATION_2', payload.otherThing)
}
Daniel S. Deboer
  • 1,566
  • 2
  • 10
  • 6
  • 4
    Why not ?.. With [vue-native-websocke](https://github.com/nathantsoi/vue-native-websocket#readme) you need to handle data from a ws server in a mutation (SOCKET_ONMESSAGE)... And their is no way to call an action from a mutation neither. – Constantin De La Roche Jan 14 '20 at 22:42
142

For the record. To call other mutations from a mutation method do it like this:

const mutations = {
    mutationOne(state, payload){
        this.commit("mutationTwo", payload)
    },
    mutationTwo(state, payload){
        console.log("called from another mutation", payload)
    }
}
Kubwimana Adrien
  • 2,463
  • 2
  • 8
  • 11
  • 10
    Is this a new(ish) feature of Vuex? Surprised that it took two and a half years for someone to point out the clearly correct answer. – Michael Hays Aug 27 '19 at 16:52
  • 4
    I'm not quite sure if this is considered as a best practice. However, this answer has **directly answered the question.** without providing alternatives that has been suggested by others via `action`. I have tested it and it seems to work well. I think this answer should be on top instead. This solution has already been discussed in [Vuex Github - Please add an ability to call mutation from another mutation #907](https://github.com/vuejs/vuex/issues/907) if anyone interested in reading more proof. – Irfandy Jip Sep 27 '19 at 07:39
  • 42
    By the way, if your `modules` is namespaced, even though it's under the same file you have to access it via `this.commit('modulesName/mutationName')` just in case if anyone was wondering. If you need more info, it's always a good reminder to just do `console.log(this)` inside the mutation, it seems that it holds the same instance as `Vue`, which you can also access `$route` too from there. – Irfandy Jip Sep 27 '19 at 07:50
  • Also, `this.commit` only works if mutations are not arrow functions. `mutationOne(state, payload){` vs `mutationOne: (state, payload) => {` because if they are arrow functions, then 'this' is undefined. – CenterOrbit May 25 '23 at 18:44
  • @IrfandyJip (and other passers-by): I've had a good long look at this, and I disagree that this answer should be on top. Having read the referenced git-hub thread, it seems to conclude that using `actions{}` is the correct answer (mainly because of how Vuex logs state changes). More fundamentally though, I think it makes logical sense to structure code in such a way that `mutations` only manage state changes and `actions` manage a flow of state-changes (i.e. some sort of higher level "feature" of the overall application). – cartbeforehorse Jun 29 '23 at 09:51
  • @IrfandyJip One could argue that 3 different properties within the `state` are functionally linked together, and that therefore they should all be changed within the same `mutation`. Okay, that's fine. But why not just directly access/update those three `state` properties within the mutation. No need to `commit` them via other mutations. On the other hand, if these 3 properties are only occasionally related (and therefore don't fit neatly into a single `mutation`), then I'd point out that your scenario is more of a "process flow" relationship, and that you should revert back to `actions`. – cartbeforehorse Jun 29 '23 at 09:56
65

When you are already doing a mutation, there is no way to commit another mutation. A mutation is a synchronous call which changes the state. Within one mutation, you will not be able to commit another mutation.

Here is the API reference for Vuex: https://vuex.vuejs.org/en/api.html

As you can see, a mutation handler receives only state and payload, nothing more. Therefore you are getting commit as undefined.

In your case above, you can set the PRODUCT and CATEGORIES as part of the same mutation handler as a single commit. You can try if the following code works:

// mutations
const mutations = {
    SET_PRODUCTS_AND_CATEGORIES: (state, response) => {
        state.products = response.data.products
        state.categories = state.products.map(function(product) { return product.category})
    },
    // ...
}

EDIT: Please refer to the answer below, provided by Daniel S. Deboer. The correct method is to commit two mutations from a single action, as described in his answer.

Dimitris Maragkos
  • 8,932
  • 2
  • 8
  • 26
Mani
  • 23,635
  • 6
  • 67
  • 54
  • 9
    I'm wondering why it is not permitted to commit a mutation from another mutation? Some mutations can be so atomic that they should be used by larger mutation functions. In the current version, there should be much duplication of code due to this restriction. – Asqan Feb 16 '17 at 12:20
  • 1
    Yes, it is possible to have a commit from another mutation with no side-effects. But debugging an incorrect state change when we have mutations calling each other is going to lead to a very bad developer experience. I think that is why it is not recommended or allowed. But thankfully we have this simple solution - we can always define a single mutation that does multiple state changes, as seen in this question / answer. – Mani Feb 16 '17 at 13:19
  • 1
    IMHO, the answer is below (commit two things from one action). It's cleaner and more readable. – JCKödel Jan 21 '18 at 18:39
  • 1
    @JCKödel I agree, the correct answer should be the one below (commit two mutations from the action), provided by Daniel. I will put a note in my answer above, to refer to the better answer below. – Mani Jan 22 '18 at 15:54
  • 1
    @Mani why this is not preferred over Dani's answer? I thought this was semantically more correct from Vuex standpoint since action is designed primarily for async operations. – Alex Napitupulu Apr 10 '18 at 19:11
  • 1
    @AlexNapitupulu Usually we have a mutation handler doing only one simple and straightforward thing at a time - either set products or set categories, not both. Based on the requirements of this question, we need to do two things at the same time. It is better to push this complexity into an action rather than a mutation. Mutations are more internal to the store and closer to the actual data, while actions are the ones that are exposed to components. So it is logical to have an action that does what the component specifically wants. – Mani Apr 11 '18 at 03:17
  • 1
    @AlexNapitupulu Regarding async operations, both these approaches are equivalent as the action in its entirety is asynchronous. Once the action is initiated asynchronously, it does not matter whether it commits two mutations one after another or calls a special mutation handler that does two things in the store. It is a design choice, but I think it is best to keep this complexity closer to the component (in the action) than handle it further down in a mutation. – Mani Apr 11 '18 at 03:21
35

To share code between mutations, you must create a new function that performs the work, which you can then reuse. Fortunately, mutations are just plain old functions, and we can pass the state parameter around however we like, so this is quite easy to do.

For example:

const mutations = {
 SET_PRODUCTS: (state, response) => {
   state.products = response.data.products
   setCategories(state)
 },
 SET_CATEGORIES: (state) => {
   setCategories(state)
 }
}

function setCategories(state) {
  state.categories = state.products.map(product => product.category)
}
Daniel Buckmaster
  • 7,108
  • 6
  • 39
  • 57
18

And if I have some common code that affects state between multiple mutations, I have to duplicate the same code on all my mutations? Or there's a better way to do that?

Nacho
  • 199
  • 4
9

Reading the Vuex documentation on Actions, it's quite clear what they are made for.

  • commit mutations instead of mutating the state
  • can contain arbitrary asynchronous operations

Actions can (not must) contain asynchronous code. In fact, the following example is correct

increment (context) {
   context.commit('increment')
}

I do not see any issue in using actions for performing multiple mutations.

Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Wanny Miarelli
  • 726
  • 2
  • 15
  • 31
4

In your case you should consider having only one mutation, namely SET_PRODUCTS.

// mutations
const mutations = {
 SET_PRODUCTS: (state, response) => {
   state.products = response.data.products
   state.categories = state.products.map(function(product) { return product.category})
 }
}

You should never have any need to call SET_CATEGORIES separately. Think about it! Categories can only mutate if products are changed. And products can change only through SET_PRODUCTS.

jiv-e
  • 483
  • 6
  • 8
3

Edit : I stumbled upon a very similar problem and the solution for me was to use a vuex getter : https://vuex.vuejs.org/en/getters.html
Your categories is actually a "computed" version of your products. Having categories as a getter allows you to keep them in sync with products and avoids duplicating the data in your store.

For the sake of answering the question in the title i leave my original answer.
An alternative to Daniel Buckmaster solution :

const mutations = {
 SET_PRODUCTS: (state, response) => {
   state.products = response.data.products
   this.SET_CATEGORIES(state)
 },
 SET_CATEGORIES: (state) => {
   state.categories = state.products.map(product => product.category)
 }
}

As you can see you could directly call the mutation itself. (as Daniel said, they are just plain functions after all)
I believe that this is a more appropriate answer to the original question : it is an actual way of composing mutations without code duplication or extra functions

Guillaume Meral
  • 452
  • 2
  • 8
  • _Calling_ a mutation function **is not the same** as _committing_ a mutation. – Emile Bergeron Jan 25 '18 at 20:56
  • Could you elaborate ? Is that really an issue since we are calling it from another mutation ? – Guillaume Meral Jan 29 '18 at 10:53
  • For example, the _handler_ for registered plugins won't be called for the second mutation. Meaning that the Vue dev tools won't show the second mutation in the list of mutations you can "time travel" to. – Emile Bergeron Jan 29 '18 at 16:04
  • But the use case is a single mutation that does two things. I understand your point but in that case you do not want to have a store state that have desynchronised lists of products and categories. – Guillaume Meral Jan 29 '18 at 16:34
  • The question is about committing two distinct mutations. A mutation that does multiple thins is another design problem already addressed in [Daniel's answer](https://stackoverflow.com/a/42452721/1218980). – Emile Bergeron Jan 29 '18 at 16:43
  • 1
    Hm yes that is what i meant by "An alternative to Daniel Buckmaster solution". It is my first StackOverflow answer, maybe i should have commented his solution instead. – Guillaume Meral Jan 29 '18 at 17:06
  • You can't comment on other people's posts yet as it takes 50 rep. An answer is ok in this case, _I just happen to not like it._ You should leave it for the sake of documenting every possible ways and someone might like it and upvote it (an upvote being 10 points, versus -2 for a dv). Don't give up on SO! – Emile Bergeron Jan 29 '18 at 17:16
  • Indeed ! This conversation made me realize that a solution to this problem could also be to have category as a getter. I edited my answer to explain a little bit. Please let me know what you think and if i should describe my reasoning a bit more. – Guillaume Meral Jan 29 '18 at 17:24
  • It was not working for me in browser. Only in unit tests. – Amio.io Jun 02 '20 at 12:52
3

I prefer to call mutations.SET_CATEGORIES(state) instead of: - calling 2 different commits from an artificial action - or doing commit() inside a mutation as it makes unit testing more difficult.

const mutations = {
 SET_PRODUCTS: (state, response) => {
   state.products = response.data.products
   mutations.SET_CATEGORIES(state)
 },
 SET_CATEGORIES: (state) => {
   state.categories = state.products.map(product => product.category)
 }
}

My opinion is that you don't need to see SET_CATEGORIES in the VueToolbox. The time travel should work anyways. Please, correct me if I'm wrong.

Amio.io
  • 20,677
  • 15
  • 82
  • 117
2

First, assign the Vue button to a variable: In main.js:

  export const app = new Vue({  
  router,
  vuetify,
  store,....

Then import the "app" variable to the js file where you define the mutation: In modules.js:

import { app } from "../../main";

You can now use it as "app.$store.commit":

mutations: {
[AUTH_SET_TOKEN]: () => {
app.$store.commit(USER_SUCCESS, params );
},...
Ali KOCA
  • 21
  • 2
1

i think

calling mutation from another mutation is bad idea because of hard to debug state and components

const mutations = {
    mutationOne(state, payload){
        this.commit("mutationTwo", payload)
    },
    mutationTwo(state, payload){
        console.log("called from another mutation", payload)
    }
}

but you can write simple function and function can reusable

function mysecondfn(state,payload){
{
// do your stuff here
}


const mutations = {
    mutationOne(state, payload){
mysecondfn(state,payload)
     },

}
Balaji
  • 9,657
  • 5
  • 47
  • 47
1

another solution that works for me:

this._mutations.mutationFunction[0]()
JeffNhan
  • 363
  • 3
  • 3
0
import spreeApi from '../../gateways/spree-api'
// initial state
const state = {
  products: [],
  categories: []
}

// mutations
const mutations = {
 SET_PRODUCTS: (state, {response,commit}) => { // here you destructure the object passed to the mutation to get the response and also the commit function
   state.products = response.data.products
   commit('SET_CATEGORIES') // now the commit function is available
 },
 SET_CATEGORIES: (state) => {
   state.categories = state.products.map(function(product) { return product.category})
 }

}

const actions = {
 FETCH_PRODUCTS: ({commit}, filters) => { // here you destructure the state to get the commit function
   return spreeApi.get('products').then(response => commit('SET_PRODUCTS', {response,commit})) // here you pass the commit function through an object to 'SET_PRODUCTS' mutation
 }
}

export default {
  state,
  mutations,
  actions
}

This should fix it. You can inject the commit into your mutation from the action so you can commit from your mutation. Hope this helps

Andrei
  • 19
  • 1
  • 4
  • 1
    Please add some explanation to your code: what exactly needs to be changed and why? Keep in mind that the OP should be able to learn from your answer – Nico Haase Feb 19 '19 at 16:08
0

you can access to all vuex

this.app.store.commit("toast/show", {
                   mssg:this.app.i18n.t('order.ordersummary.notifymessage'),
                    type: "danger",
                });

access to $i18n in vuex

this.app.i18n.t('order.ordersummary.notifymessage')
0

Use this

const mutations = {
 SET_PRODUCTS: (state, response) => {
   state.products = response.data.products
   this.commit('SET_CATEGORIES')
 },
 SET_CATEGORIES: (state) => {
   setCategories(state)
 }
}
  
Sarwar Hasan
  • 1,561
  • 2
  • 17
  • 25
  • While this code snippet may solve the problem, it doesn't explain why or how it answers the question. Please [include an explanation for your code](//meta.stackexchange.com/q/114762/269535), as that really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. – Luca Kiebel Mar 03 '22 at 12:51
0

In Vuex, mutations are synchronous functions that are responsible for modifying the state of the Vuex store. While mutations cannot directly call other mutations, you can achieve the desired behavior by utilizing actions.

Here's an example of how you can call one mutation from another mutation using actions:

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0,
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    doubleIncrement(state) {
      state.count += 2;
    },
  },
  actions: {
    incrementAndDouble({ commit }) {
      commit('increment');
      commit('doubleIncrement');
    },
  },
});

export default store;


<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="incrementAndDouble">Increment and Double</button>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  computed: {
    ...mapState(['count']),
  },
  methods: {
    ...mapActions(['incrementAndDouble']),
  },
};
</script>
Synchro
  • 1,105
  • 3
  • 14
  • 44