ChatGPT解决这个技术问题 Extra ChatGPT

Can I call commit from one of mutations in Vuex store

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.

Related (closed) issue: github.com/vuejs/vuex/issues/907
Hi @Saurabh, I have tested Kubiwama Adrien's answer, and it seems that it has what you need, maybe test it and update this forum with the latest answer? Thanks!
Why not use an action and call multiple mutations inside it?

D
Daniel S. Deboer

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)
}

Why not ?.. With vue-native-websocke 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.
K
Kubwimana Adrien

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)
    }
}

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.
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 if anyone interested in reading more proof.
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.
@IrfandyJip Thank you, your comment just saved me some hours of debugging !
M
Mani

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.


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.
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.
IMHO, the answer is below (commit two things from one action). It's cleaner and more readable.
@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 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.
D
Daniel Buckmaster

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)
}

I think this gives an error about chaining state outside of a mutation
What makes you think that? As long as you only call setCategories from within a mutation, it'll be fine.
This worked for me, but I left the function implementation as a mutation and just called mutations.setCategories(state) from the function.
unfortunately in the Typescript Vue approach this doesn't work, bc. of not being able to access/see the plain functions
N
Nacho

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?


The silence is deafening. I'd love an answer to this too.
@Nacho see the answer I just created. It's not ideal but it's the best I know of
I agree with Daniel. Functions are the way to re-use code.
Yep - a mutation is just a function that takes the state as an argument and modifies it. You can declare as many helper functions as you like and reuse them.
Please post an answer only when you have an answer. If you want to ask something, create a new thread.
E
Emile Bergeron

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.


j
jiv-e

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.


G
Guillaume Meral

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


Calling a mutation function is not the same as committing a mutation.
Could you elaborate ? Is that really an issue since we are calling it from another mutation ?
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.
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.
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.
A
Amio.io

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.


A
Ali KOCA

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 );
},...

ß
ßãlãjî

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)
     },

}

J
JeffNhan

another solution that works for me:

this._mutations.mutationFunction[0]()

A
Andrei
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


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
e
el_yonousi

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')

S
Sarwar Hasan

Use this

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

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, 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.