Adding Modules to a Vuex Store

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add modules to separate a Vuex store into smaller parts.

Dividing a Store into Modules

Vuex uses a single state tree. This means the states are located in one big object. This will be bloated is our app grows big.

To make a Vuex store easier to scale, it can be separated into modules. Each module can have its own state, mutations, getters, and actions.

The state parameter in mutations and getters are the module’s local state.

By default, all actions, mutations, and getters inside modules are registered under a global namespace. This allows multiple modules to react to the same mutation or action type.

We can divide our store into module as in the following example:

index.js :

const moduleA = {  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const moduleB = {  
  state: {  
    count: 1  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const store = new Vuex.Store({  
  modules: {  
    a: moduleA,  
    b: moduleB  
  }  
});

console.log(store.state.a.count);  
console.log(store.state.b.count);
Enter fullscreen mode Exit fullscreen mode

Then in the console.log output, we should see 0 and 1 since moduleA ‘s initial count state is 0 and moduleB ‘s initial count state is 1.

To make each module self-contained, we have to namespace it by setting the namespaced option to true .

We can namespace a module and then call dispatch on actions after namespacing the modules as follows:

index.js :

const moduleA = {  
  namespaced: true,  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const moduleB = {  
  namespaced: true,  
  state: {  
    count: 1  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const store = new Vuex.Store({  
  modules: {  
    a: moduleA,  
    b: moduleB  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: {  
    ...Vuex.mapState({  
      a: state => state.a,  
      b: state => state.b  
    })  
  },  
  methods: {  
    increaseA(payload) {  
      this.$store.dispatch("a/increase", payload);  
    },  
    increaseB(payload) {  
      this.$store.dispatch("b/increase", payload);  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increaseA({amount: 10})">Increase</button>  
      <button @click="increaseB({amount: 10})">Increase</button>  
      <p>A Count: {{a.count}}</p>  
      <p>B Count: {{b.count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

In the code above, we have namespaced: true set in each module, and then we added 2 methods to our Vue instance to dispatch the namespaced actions:

increaseA(payload) {  
  this.$store.dispatch("a/increase", payload);  
}
Enter fullscreen mode Exit fullscreen mode

and:

increaseB(payload) {  
  this.$store.dispatch("b/increase", payload);  
}
Enter fullscreen mode Exit fullscreen mode

Since we have namespaced set to true , we have to dispatch the actions by passing in “a/increase” and “b/increase” to dispatch .

Then once we clicked the buttons, our methods are called to dispatch the actions and the numbers will increase.

Register Global Action in Namespaced Modules

We can also register global actions in namespaced modules, by setting the root option to true and place the action definition in the function handler.

To do register a global action, we can write something like the following code:

index.js :

const moduleA = {  
  namespaced: true,  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const moduleB = {  
  namespaced: true,  
  state: {  
    count: 1  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const rootModule = {  
  actions: {  
    increaseAll: {  
      root: true,  
      handler(namespacedContext, payload) {  
        namespacedContext.commit("a/increase", payload);  
        namespacedContext.commit("b/increase", payload);  
      }  
    }  
  }  
};

const store = new Vuex.Store({  
  modules: {  
    a: moduleA,  
    b: moduleB,  
    root: rootModule  
  }  
});

new Vue({  
  el: "#app",  
  store,  
  computed: {  
    ...Vuex.mapState({  
      a: state => state.a,  
      b: state => state.b  
    })  
  },  
  methods: {  
    ...Vuex.mapActions(["increaseAll"])  
  }  
});
Enter fullscreen mode Exit fullscreen mode

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increaseAll({amount: 10})">Increase All</button>  
      <p>A Count: {{a.count}}</p>  
      <p>B Count: {{b.count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

In the code above, we added the rootModule which has the global action as follows:

const rootModule = {  
  actions: {  
    increaseAll: {  
      root: true,  
      handler(namespacedContext, payload) {  
        namespacedContext.commit("a/increase", payload);  
        namespacedContext.commit("b/increase", payload);  
      }  
    }  
  }  
};
Enter fullscreen mode Exit fullscreen mode

A global action has the root option set to true and a handler method which is used to dispatch actions from any module.

Then in the Vue instance, we have:

methods: {  
  ...Vuex.mapActions(["increaseAll"])  
}
Enter fullscreen mode Exit fullscreen mode

to map the increaseAll action to a method in the Vue instance.

Then in the template, we have:

<button @click="increaseAll({amount: 10})">Increase All</button>
Enter fullscreen mode Exit fullscreen mode

to call the increaseAll method returned from the mapActions method when the button is clicked.

Then we should both numbers increasing since we mapped both module’s state to the Vue instance’s data.

Dynamic Module Registration

We can also register a module dynamically by using the store.registerModule method as follows:

index.js :

const moduleA = {  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state, payload) {  
      state.count += payload.amount;  
    }  
  },  
  actions: {  
    increase({ commit }, payload) {  
      commit("increase", payload);  
    }  
  }  
};

const store = new Vuex.Store({});

store.registerModule("a", moduleA);

new Vue({  
  el: "#app",  
  store,  
  computed: {  
    ...Vuex.mapState({  
      a: state => state.a  
    })  
  },  
  methods: {  
    ...Vuex.mapActions(["increase"])  
  }  
});
Enter fullscreen mode Exit fullscreen mode

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <button @click="increase({amount: 10})">Increase</button>  
      <p>A Count: {{a.count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

The store.registerModule takes a string for the module name, and then an object for the module itself.

Then we can call the helpers to map getters to computed properties and actions/mutations to methods. In the code above, we have the increase action mapped to a method.

Then we can call it in our template as usual.

Conclusion

If our Vuex store is big, we can divide it into modules.

We can register modules when we create the store or dynamically with registerModule .

Then we can map actions/mutations by their name as usual, and we can map the state by accessing state.a.count , where a is the module name, and count is the state name. Replace it with our own module and state names if the code is different.

We can also namespace the modules. Then we dispatch the actions starting with the module name and a slash instead of just the name.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .