Cleaner Centralized State Management in Vue with Vuex Modules

Cleaner Centralized State Management in Vue with Vuex Modules

Vuex, the official state manager for Vue applications, comes up with many nice features that help us to organizing our projects. One of them, on which we will focus in this article, are the

__Vuex Modules__.


Vuex modules allow us to divide our store into modules, each of which with its own state, mutations, actions, getters, and even nested modules. This will avoid the store to become a huge fragile soap bubble as the application grows.

We will take as example a simplified version of a company’s management app, where the store contains all the logic for mantaining an internal inventory and for the administration of the human resources.
Here it is the initial state of the Vuex store, inserted into a single file:

 

 

```js
// 'src/store/index.js'
import Vuex from 'vuex';
import Vue from 'vue';

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        products: [
            // {id, quantity}
        ],
        
        resources: [
            // {id, role}
        ],
    },
    
    mutations: {
        setProducts(state, products) {
            state.products = products;
        },
        
        incrementProductQuantity(state, productIndex) {
            state.products[productIndex] += 1;
        },
        
        setResources(state, resources) {
            state.resources = resources;
        },
    },
    
    actions: {
        setProducts({ commit }, products) {
            commit('setProducts', products);
        },
        
        async incrementProductQuantity({ commit }, productId) {
            const productIndex = stat.products.findIndex(prod => prod.id === productId);
            
            commit('incrementProductQuantity', productIndex);
        },
        
        setResources({ commit }, resources) {
            commit('setResources', resources);
        },
    },
    
    getters: {
        getProducts(state) {
            return  [...state.products];  
        },
        
        getResourceById(state) {
            return function(id) {
                return state.resources.find(res => res.id === id);
            }
        },
    },
});

export default store;
```

As you can see, lot of necessary logic is not reported, like backend calls, because the main goal is to properly use Vuex modules to correctly split out the store, according to the functions of the application: the inventory and the managing of human resources. Therefore details have been reduced to minimum.

## The first step

Firstly, let’s create two modules: products and resources.

Products:

```js
// 'src/store/modules/products/index.js'
const products = {
    state: { 
        products: [
            // {id, quantity}
        ],
    },
    
    mutations: { 
        setProducts(state, products) {
            state.products = products;
        },
        
        incrementProductQuantity(state, productIndex) {
            state.products[productIndex] += 1;
        },
    },
    
    actions: {
        setProducts({ commit }, products) {
            commit('setProducts', products);
        },
        
        async incrementProductQuantity({ commit }, productId) {
            const productIndex = stat.products.findIndex(prod => prod.id === productId);
            
            commit('incrementProductQuantity', productIndex);
        },
    },
    
    getters: {
        getProducts(state) {
            return  [...state.products];  
        },
    }
}

export default products;
```

Resources:
```js
// 'src/store/modules/resources/index.js'
const resources = {
    state: { 
        resources: [
            // {id, role}
        ],
    },
    
    mutations: { 
        setResources(state, resources) {
            state.resources = resources;
        },
    },
    
    actions: {
        setResources({ commit }, resources) {
            commit('setResources', resources);
        },
    },
    
    getters: {
        getResourceById(state) {
            return function(id) {
                return state.resources.find(res => res.id === id);
            }
        },
    }
}

export default resources;
```

Now we have to update _store/index.js_in line with the previous modifications:

```js
// 'src/store/index.js'
import Vuex from 'vuex';
import Vue from 'vue';

import productsModule from './modules/products';
import resourcesModule from './modules/resources';

Vue.use(Vuex);

const store = new Vuex.Store({
   modules: {
       productsModule,
       resourcesModule
   }
});

export default store;
```

It is noteworthy that the store created is always unique, but thanks to the `modules` field we can plug-in as many modules as we want.However, a question arise: is this only a mere file suddivision? The answer is, because now the state is divided in turn:

```js
// 'src/store/index.js'

// ...

store.state.productsModule; // productsModule's state
store.state.resourcesModule; // resourcesModule's state

export default store;
```

This implies following three consequences:

      Inside a module’s mutations and getters, the first argument received will be the module’s local state
```js
// 'src/store/modules/products/index.js'

const products = {
   
    mutations: { 
        setProducts(state, products) {
            // state is the local module state
            // state === store.state.productsModule (the store into 'src/store/index.js')
        },
        
    },
    
    //...
    
    getters: {
        getProducts(state) {
            // state is the local module state
            // state === store.state.productsModule 
        },
    }
}
```
      Inside module’s actions, _context.state_ will expose the local state, and root state will be exposed as _context.rootState_
```js
// 'src/store/modules/products/index.js'

const products = {
   
    actions: {
        setProducts(context, products) {
            // context.state is the local state
            // context.rootState === store.state
            // context.rootState.resourcesModule === store.state.resourcesModule
        },
        
        // ...
    },
}
```
      Inside module’s getters, the root state will be exposed as their third argument
```js
// 'src/store/modules/products/index.js'

const products = {
   
   //...
    
    getters: {
        getProducts(state, globalGetters, rootState) {
            // rootState === store.state
            // rootState.resourcesModule === store.state.resourcesModule
        },
    }
}
```

The second argument is called __globalGetters__ because, by default, actions, mutations and getters inside modules are still registered under the global namespace.

## Namespaced modules
If we want more self-contained modules, we can set the `namespace` flag to `true`. When the module will be registered, all of its getters, actions and mutations will be automatically namespaced based on the path the module is registered at:

```js
const exampleModule = {
    namespaced: true,
    state: { ... },
    mutations: { ... },
    actions: { ... },
    getters: { ... }
}
```

Namespaced getters and actions will receive localized `getters`, `dispatch` and `commit`. So you can use the module assets without writing prefix in the same module. Toggling between namespaced or not does not affect the code inside the module, but it differs the way other modules access to it.

If you want to use global `state` and `getters`, `rootState` and `rootGetters` are passed as the 3rd and 4th arguments to getter functions, and also exposed as properties on the `context` object passed to action functions. To dispatch actions or commit mutations in the global namespace, pass `{ root: true }` as the 3rd argument to `dispatch` and `commit`.

```js
// 'resources' module example
modules: {
  resources: {
    namespaced: true,
	
	// ...
    
   
	actions: {
	
		// dispatch and commit are localized for this module
		// they will accept `root` option for the root dispatch/commit
        setProducts({ commit }, products) {
            commit('setProducts', products); // noting has changed
        },
        
        async incrementProductQuantity({ commit, dispatch }, productId) {
            const productIndex = stat.products.findIndex(prod => prod.id === productId);
            
            commit('incrementProductQuantity', productIndex);
			
			// eventually after async request...
			dispatch('setProducts', 0) // -> 'resources/setProducts'
			// or
			dispatch('resources/setProducts', null, { root: true }, 0) // -> 'resorces/setProducts'
			
        },
    },
    
    getters: {
	    // `getters` is localized to this module's getters
        getProducts(state, getters, rootState, rootGetters) {
			
			// rootGetters.resources === getters
			// rootGetters.products contains allthe getters defined locally into the 'products' module
            return  [...state.products];  
        },
    }
  }
}
```

## Conclusion

The Vuex modules pattern is the best way to properly organize the centralized store in our projects. This specifically because it is maintained directly by the Vue core team and it is extremely simple, as all the Vue world. So, what are you waiting for?

Leave a Reply

Your email address will not be published.