DEV Community

Cover image for 🚩 Vuex Pattern: Smart Module Registration
Nikos Koikas
Nikos Koikas

Posted on

🚩 Vuex Pattern: Smart Module Registration

Vue Experience: ⚫️⚫️⚫️⚫️⚪️
Vuex Experience: ⚫️⚫️⚫️⚫️⚫️

Have your ever tried to manage your's application state?
Large applications can often grow in complexity, due to multiple pieces of state scattered across many components and the interactions between them. So, Vue offers Vuex but as official documentation says:

Vuex is using a single state tree, all state of our application is contained inside one big object. However, as our application grows in scale, the store can get really bloated.

To help with that, Vuex allows us to divide our store into modules. Each module can contain its own state, mutations, actions, getters, and even nested modules.


I think you have already got confused, so let's go into code.

# This is a classic store structure with modules
├── index.html
├── main.js
├── components
└── store
    ├── index.js    # where we assemble modules and export the store
    └── modules
        ├── auth.js
        ├── posts.js
        └── comments.js
Enter fullscreen mode Exit fullscreen mode

As you can see we have a store folder with an index.js and a subfolder named modules, which contains all modules. But module registration can start to get tedious.
index.js in store/

import Vue from 'vue'
import Vuex from 'vuex'
import auth from './modules/auth'
import posts from './modules/posts'
import comments from './modules/comments'
// ...

Vue.use(Vuex)
export default new Vuex.Store({
    modules: {
        auth,
        posts,
        comments,
        // ...
    }
})
Enter fullscreen mode Exit fullscreen mode

Example scaffolding for Vuex modules.

export default {
  namespaced: true,
  state: {},
  getters: {},
  mutations: {},
  actions: {}
}
Enter fullscreen mode Exit fullscreen mode

This is the standard way for registering modules. If you know what namespacing is, go on.

By default, actions, mutations and getters inside modules are still registered under the global namespace - this allows multiple modules to react to the same mutation/action type.
If you want your modules to be more self-contained or reusable, you can mark it as namespaced with namespaced: true

Let's see Module Registration as mentioned by Chris Fritz(Vue core member) in a VueConf.

🚩 Firstly, let's add a index.js file in store/modules/

# This is our store structure with modules
├── index.html
├── main.js
├── components
└── store
    ├── index.js      # where we assemble modules and export the store
    └── modules
        ├── index.js  # this is the js file that solves the problem
        ├── auth.js
        ├── posts.js
        └── comments.js
Enter fullscreen mode Exit fullscreen mode

🚩 Then let's modify this index.js in store/modules/index.js

import camelCase from 'lodash/camelCase'
// Storing in variable a context with all files in this folder
// ending with `.js`.
const requireModule = require.context('.', false, /\.js$/)
const modules = {}

requireModule.keys().forEach(fileName => {
    if (fileName === './index.js') return
    // filter fullstops and extension 
  // and return a camel-case name for the file
    const moduleName = camelCase(
        fileName.replace(/(\.\/|\.js)/g, '')
    )
  // create a dynamic object with all modules
    modules[moduleName] = {
    // add namespace here
        namespaced: true,
        ...requireModule(fileName).default
    // if you have exported the object with name in the module `js` file
    // e.g., export const name = {};
    // uncomment this line and comment the above
        // ...requireModule(fileName)[moduleName]
    }
})
export default modules
Enter fullscreen mode Exit fullscreen mode

🚩 Let's delete namespacing from every module js file.

// export const name = { if you want to export an object with name
export default {
  // namespaced: true,   delete this line 
    state: {},
    getters: {},
    mutations: {},
    actions: {}
}

Enter fullscreen mode Exit fullscreen mode

🚩 Finally the code from above, where we had to import all the modules can change to:
index.js in store/

import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'

Vue.use(Vuex)
export default new Vuex.Store({
    modules
})
Enter fullscreen mode Exit fullscreen mode

I think we have done an ''automated'' system that includes every file in modules folder. A smarter and cleaner code.

Until next time...Happy coding!

Top comments (9)

Collapse
 
denisinvader profile image
Mikhail Panichev

Hi!
I've also watched this video and inspired by the parts about auto registration for vuex modules and components. I've tried these techniques in my workflow, but I've found that they are less flexible than manual imports.

For example: I want to add axios hook for 401 response code and unauthorize the user in this case. If I have auto-module-registration, I have to define this hook somewhere outside the store. I think it's better to define this hook in the module definition, by exporting vuex plugin function from module and then register this plugin in store index file. Something like this:

// auth module
export const init = store => {
  store.dispatch('checkAuth');

  axios.interceptors.response.use(null, error => {
    if (error.response && error.response.status === 401 && store.getters.isUserAuthorized) {
      store.commit(types.USER_LOGOUT);
    }

    throw error;
  });
};

// store index
const store = new Vuex.Store({
  modules { ... },
  plugins: [
    loginModule.init,
  ],
});

Enter fullscreen mode Exit fullscreen mode

in conclusion I think that you described a good technique and it's great for boilerplates and fast prototyping, but it's not a very flexible

Collapse
 
nkoik profile image
Nikos Koikas

I agree with you.
Of course, you need a good experience with Vuex. In your example, auth.js module file has different workflow from standard modules.
Every project is different. As you said above, this technique make your development faster and (cleaner), but loses a little bit from flexibility. Before designing the architecture of a project's store, you have to consider the pros and cons of this auto-registration pattern and if it suits to your project.

Collapse
 
vasekprochazka profile image
Václav Procházka

Hey,
I've also tried this autoloader. There is one problem I found. When I want to start my tests (tests for modules in vuex), the jest fails on: TypeError: require.context is not a function. Does anybody has solution for this?

Collapse
 
morgothx profile image
Info Comment hidden by post author - thread only accessible via permalink
Camilo Castro

Hi, thank you for the post.

I have a question if you can help me.

I have the following folder structure in my project

project
|
-store
|
|-constants.js
|-modules
|
|-someModule
|-otherModule
|-index.js (just as in your post)

This is a sample from my constants file

//Actions
export const USER_SET = 'user/SET'
export const USER_GET_ROLES = 'user/GET_ROLES'
//Mutations
export const USER_SET_BY_KEY = 'user/SET_BY_KEY'
export const USER_SET_ROLES = 'user/SET_ROLES'

And in my modules I use the constant as name for the actions, mutations, getters, like in this sample

const actions =
{
[constants.USER_SET]: ({commit}, user) =>
{
let response = {'data': user, 'key': 'user' }
commit(constants.USER_SET_BY_KEY, response)
},
}

The idea is that when I use in components, i use the constants file name too, this way I can change the name in one place

Like in this

...mapActions({
getRoles: constants.USER_GET_ROLES,

The thing is, even when the module are dinamically loaded by the index in the modules folder, Vuex doesn't recognize the actions, mutations, getters

I print the modules object in the index and i see that it has this properties empty

{
"customer": {
"state": {
"customers": null,
"contacts": null
},
"actions": {},
"mutations": {},
"getters": {}
},
.
.
.
.

}

If I go back to import every module manually and put then in the vuex individually, then vuex recognize the actions, mutations, getters

So thank you in advance if can you please point me in the right direction

p.s Sorry for my bad english, it's not my native language

Collapse
 
morgothx profile image
Camilo Castro

Nevermind, It was a problem with the way I was telling vuex to use modules but thank you anyway

Collapse
 
heshamelmasry77 profile image
Hesham El Masry

okay but how to use it after we do that for example i used to do this :
context.commit('products/decrementProductInventory', product, {root: true})
how i am gonna use it now ?

Collapse
 
petersonbrand profile image
Peterson Brand

Very interesting!
Would you like to do the same with router modules?

Collapse
 
imtiaz101325 profile image
Sk Imtiaz Ahmed

My unit tests are failing because jest can't figure out what require.context is 🤦

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more