vuejs / vuex

🗃️ Centralized State Management for Vue.js.
https://vuex.vuejs.org
MIT License
28.42k stars 9.58k forks source link

How do you get rootState in mutation from modules? #344

Closed zipme closed 8 years ago

zipme commented 8 years ago

Hi,

I am currently using vuex 2.0.0-rc.6 and trying to get the rootState in a mutation method from within a module, like so:

  const moduleA = {
    state: { count: 0 },
    mutations: {
      increment: (state, rootStatem, { by }) {
        // state is the local module state
        state.count = state.count + by

        // rootState is undefined
      }
    },
 ...

and then store.commit('increment', {by: 2})

As you can see the rootState is undefined. Any idea why this happened? I am referring the code from http://vuex.vuejs.org/en/modules.html

ktsn commented 8 years ago

I think we had never implemented such feature... @yyx990803 Is this planning to implement?

zipme commented 8 years ago

Should we update the guide then?

Here is the code snippet taken from the guide:

cosnt moduleA = {
  state: { count: 0 },
  mutations: {
    increment: (state, rootState) {
      // state is the local module state
      state.count++

      // rootState is passed as the second argument, but you should not
      // mutate it from within a module.
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}
zipme commented 8 years ago

But I do think this feature can be useful in some cases.

I am currently using vue-router together with vuex-router-sync, I would like to access the router property of state inside the mutation, but seems it's only available in rootState. So either we expose the rootState in the mutation, like in the doc, or we inject the router into all modules' state. What do you think?

ktsn commented 8 years ago

I think it is nice as an escape hatch but we should expose it as the 3rd argument because it would be hardly used in module mutations. In my opinion, we had better to use actions to access other modules state instead of in mutations.

zipme commented 8 years ago

yes, that makes sense, I will use action instead of mutation to access rootState. Thanks for the answer!

blake-newman commented 8 years ago

IMO, mutations should not have rootState.

An action is the intent of a mutation, and the mutation is the finalisation of that intent.

Therefore with that reasoning. Actions should be used to collate the data to commit to that the mutation.

@yyx990803 Would you agree?

zipme commented 8 years ago

@blake-newman Indeed this is a good reason to use actions instead of mutations, however, I am in a situation where changing the state should also update the url (by calling router.go) what would be the best way to do this?

And a related question, I understand that the state should be the single source of truth, but so is the url. I often find myself syncing between url and the state, currently I am trying to change the url when the state is changed, should I maybe do this the other way around by updating the state in the activate callback?

LinusBorg commented 8 years ago

however, I am in a situation where changing the state should also update the url (by calling router.go) what would be the best way to do this?

  1. Import the router into the module where your action is
  2. call your mutation
  3. use rootState of the action to get the desired route information
  4. use route.go() (or route.push() if using 2.0)

I am trying to change the url when the state is changed, should I maybe do this the other way around by updating the state in the activate callback?

Probably.

ktsn commented 8 years ago

/ping @yyx990803 What do you think about this? (the doc says module mutations will receive rootState their 2nd argument)

theshem commented 8 years ago

I spent an hour to figure out that it is an inconsistency with the API mentioned in the doc :/ wasn't able to get the payload.

eloiqs commented 8 years ago

If i'm not mistaken, the purpose of modules is to allow encapsulation. I'm at a point in my app where my root store's mutations are beginning to be hard to manage and I don't even consider my app to be "mutation heavy" if we can say so. I could easily foresee cases way worst than mine. Those mutations are all committed from module actions but I can't afford to have the mutations be in their respective modules because a lot of my modules depend on the same state. :worried:

I could probably refactor my stores and sync the local module states with the root state somehow but I would still end up needing mutations in my root state to handle the bindings. Anybody came across an elegant solution?

John0x commented 7 years ago

Can we please reopen this? I need the rootState for some special cases.

LinusBorg commented 7 years ago

If you can explain why having access rootState in actions is not enough for your special cases, we can talk about it.

goalie7 commented 7 years ago

In my case, I want to set track the user interaction with the webpage so i think mutations are the right place to keep track of that. I could put it on actions too, but i think that having it on mutations keep the code cleaner.

This way in the rootState i will have the tracker tool I use to call the remote apis , and in submodules i could do something like:

rootState.tracker.track('some event here')

I will give a try to put it on actions

LinusBorg commented 7 years ago

In my case, I want to set track the user interaction with the webpage so i think mutations are the right place to keep track of that. I could put it on actions too, but i think that having it on mutations keep the code cleaner.

That sounds more like a perfect usecase for a plugin: https://vuex.vuejs.org/en/plugins.html

goalie7 commented 7 years ago

@LinusBorg I will take a look, thanks!

chrisbell08 commented 7 years ago

Ok so I think I have a scenario when rootState could be useful. So I have multiple collections and each collection has multiple videos. My store looks like this:

state: {
collections:[collectionOne:{
videos:[{videoOne: {
is_favourite: 0
}}]
}]
}

I think you get the idea, so inside my video component I fire an action in the video module:

 favourite(context, data){
    context.commit('setFavourite', data)
  },

Then my mutation in the videos module is:

  setFavourite({state, getters, rootState, rootGetters}, data) {
    let videos = rootState.collections[ getKeyFromId(data.collectionId, rootState.collections) ]

    videos[ getKeyFromId(data.id, videos) ].is_favourite = data.val;
  },

Now the only way I can think of doing this at the moment is the video action fires a mutation in the Collections module but that kind of negates the point of having the modules.

Apologies if I missing something obvious, they're may be a way to do what i'm doing much better that I haven't thought of so open to suggestions.

ghost commented 7 years ago

I have a complex view with a complex model:

export default class RootClass extends BaseClass {
  constructor(){
    this.complexClass1 = new ComplexClass1()
    this.complexClass2 = new ComplexClass2()
    this.complexClass3 = new ComplexClass3()
    this.complexClass4 = new ComplexClass4()
    this.complexClass5 = new ComplexClass5()
    //....30 some props, some functions, etc...
  }
}

My vuex structure is something like this:

/store
  /modules
    class1.js
    class2.js
    class3.js
    class4.js
index.js

What I want to do is query the api and load this complex model and store it in the root of vuex, and then be able to mutate that root state from the vuex submodules.

What I have to do now is call api, and commit the root class, commit to each vuex module (class 1, class 2, class 3, etc.) That is just for loading data. If I want to clear, I have to do the same.

Maybe I am doing this all wrong. I haven't come across any good examples for a large scale app.

AshwinTayson commented 6 years ago

Any update on this? Having the rootState is really helpful, but if that is not going to be implemented atleast mention a workaround.

LinusBorg commented 6 years ago

The "workaround" (I would say: intended usage) is to repare the data in the action, which does have access to rootState

I'm also confused why you expect an update on a closed issue.

hawkeye64 commented 6 years ago

Consider this. An action is used to fetch data. The data is agnostic and could be used as state in multiple areas based on some criteria held in a different state.

A good example of this is using PostreSQL notifications. The table name and id is sent to the client. The client says, ok, I am currently displaying data from that table so I want it. The action is called to fetch the data. The action then calls the mutation. The mutation now must decide if the data should be retained based on filtering kept in a different state and it needs rootState to do this.

Now, you might ask why not do the filtering right at the action where it has access to rootState. Say there are 4 different states that could potentially use this data and each of them have their own filtering criteria. That's a lot of logic to handle a "what-if" scenario in an action. Whereas, each mutation can cherry-pick it's own data with either accept or not. The logic is better in the mutation which is more readable and decisive and less prone to bugs.

PierBover commented 6 years ago

I agree with @hawkeye64 it makes a lot more sense to do sync data massaging in a mutation.

Anyway, I won't waste time debating this. Many people here tried presenting valid arguments in favor of adding rootState to mutations, but it's evident (in this issue and others) these debates always end up with the same answer: "you are holding it wrong".

jpsala commented 6 years ago

Sorry to revive this but in the documentation it says (i think) that actions are for async, so it does not discourage the direct use of mutations, in that case it seems ok to have rootState in there.

jeduden commented 5 years ago

@hawkeye64 if you use the data in multiple spots. have you considered to store the data in one place and use getters in the different places that depend on it ?

hawkeye64 commented 5 years ago

@jeduden That's exactly what I do. But it could have been even more simplified in the mutation (it's supposed to be a mutation of the data!) if it had access to rootState.

simonwep commented 5 years ago

It's possible to access it via this.state inside a mutation (since this refers to the vuex store), not sure if this is best practice though.

jenstornell commented 5 years ago

@Simonwep Brilliant! I can confirm that it works. No need to hassle with actions in this case. It's not an async ajax call anyway.

onekiloparsec commented 5 years ago

The alternative is to use a plugin. I am using it to sync things between different namespaced store modules. With the risk of triggering infinite loops, but that's a risk I can take.

payalord commented 4 years ago

It's possible to access it via this.state inside a mutation (since this refers to the vuex store), not sure if this is best practice though.

@Simonwep didn't worked for me. I've got error Cannot read property 'state' of undefined. I think I will pass rootState from action to my mutation.

willjblair commented 4 years ago

It's possible to access it via this.state inside a mutation (since this refers to the vuex store), not sure if this is best practice though.

@Simonwep didn't worked for me. I've got error Cannot read property 'state' of undefined. I think I will pass rootState from action to my mutation.

Does your mutation look like this:

mutation: (state, payload) => { state.value = payload }

or this:

mutation(state, payload) { state.value = payload }

The binding of 'this' is different for arrow functions. If yours is the former, try changing to the second function type.

mendmania commented 2 years ago

You do not need to access a root module from a module, you should call specific mutation using {root: true}. commit('nameModule/COMMIT_NAME', data, { root: true })