Dynamic Modules with Vuex and Vue

Francesco Vitullo
codeburst
Published in
5 min readOct 30, 2017

--

While using Vue and Vuex, I have gone through different scenarios with complex facets and with this article I would like to share my own thoughts regarding the creation of modules with a Vuex’ store.

During my dev experiences, I had the chance to develop apps with React and Redux, Flux, etc… so the flow introduced by Vuex is pretty familiar to me and I haven’t had hard time absorbing it or been struggling with applying my mindset to it. Indeed, what’s different, it’s the way Vuex suggests to start: a single store with global actions, mutations, getters, state and name spaced modules (if needed). Instead of considering multiple stores, I’d rather focus on name spaced modules with dynamic initialisation, since global and shared modules are pretty tricky while building a big codebase.

With this article, I am not laying down best practices but I would like to discuss how to deal with dynamic modules, re-use and re-instantiating single module across components in order to have better isolation.

Why? Because isolation of the concept feels safer, cleaner and gives a better understanding about the flow and where the data is being manipulated.

Following Vuex documentation, it’s quite easy to create name spaced modules, use the exposed helpers to connect the modules with created components and dynamically register them.

First of all, let’s start defining name spaced modules:

// store/modules/cart/index.jsimport * as actions from './actions';
import * as getters from './getters';
import * as mutations from './mutations';
const state = {
items: 0
};
const namespaced = true;export default {
namespaced,
state,
actions,
getters,
mutations
};

// store/index.js
import cart from './modules/cart/index';
export default new Vuex.Store({
...
modules: {
cart
},
...
})

According to documentation, when thenamespaced property is avoided, it will register the modules under the global namespace and allow multiple modules to react to the same mutation/action type.
Then, as discussed previously, thanks to the namespaced property, we are able to isolate every module and manage it correctly (without expecting side effects).

Now that you are more familiar with this technique, I would propose another scenario: how to re-use a single module across components and isolate the data?

Answer is: we can rely on dynamically created modules and connect them with the components we want.

How: Vuex is exposing a way to register modules at runtime with the registerModule function:

// register a module `myModule` 
store.registerModule('myModule', { // ... })
// register a nested module `nested/myModule` store.registerModule(['nested', 'myModule'], { // ... })// to unregister a module `myModule`
store.unregisterModule('myModule')

Being really straight forward, now we can think about all the ways to register/unregister a module at runtime and re-use the same basic module wherever it makes sense.

For example, we might have an application with few pages where every page has an assigned component (few similar components) which need to be connected with the same module we want to re-use but keeping the isolation of data in mind(because different components might have different behaviours). Since the components differ but the module we want to use it’s the same, we can proceed defining a common module and register a new instance for every component we have (avoiding to create n modules for m components).

In the sample below, the component will display a title and a randomly generated variable associated to the module’s state.

Let’s start from the common module:

import actions from './actions';
import getters from './getters';
import mutations from './mutations';

const namespaced = true;

export default {
namespaced,
state () {
return {
items: 0
}
},
actions,
getters,
mutations
};

Everything looks like the same, except state . In the snippet above, we are exposing a function instead of a classic variable because we would like to create a relative state within the module’s context which will differ from the other instances.

  • Types:
export default {
SUCCESS: 'SUCCESS'
};
  • Actions:
import types from './types';const randomInt = (min, max) => ...;const generate = ({ commit, state }, options = {}) => {
const { min = 0, max = 100 } = options;
// async mock
setTimeout(() => {
commit(types.SUCCESS, {
items: randomInt(min, max)
});
}, 5000);
};

export default {
generate
};
  • Mutations:
import types from './types';

const success = (state, obj) => {
state.items = obj.items;
};

export default {
[types.SUCCESS]: success
};
  • Getters:
const getItems = (state) => {
return state.items;
};

export default {
getItems
};

Of course, the Vuex’s instance will be declared with empty modules while creating a new one.

An affected component might look like the following one:

<template>
<div>
Home
<div>
Items: {{items}}
</div>
</div>
</template>

<script>
import { mapState, mapActions } from 'vuex';
import commonModule from '../store/modules/common/index';
const name = 'home'; export default {
created: function () {
const store = this.$store;
// register a new module only if doesn't exist
if (!(store && store.state && store.state[name])) {
store.registerModule(name, commonModule);
} else {
// re-use the already existing module
console.log(`reusing module: ${name}`);
}
},
computed: {
// map the state from the given namespace
...mapState(name, {
items: state => state.items
})
},
methods:{
// map actions from the given namespace
...mapActions(name, ['generate'])
},
mounted: function () {
this.generate();
},
};
</script>

For the sake of the example, we are using the created hook to register the new module and mapping the state and actions within the component. Of course, the instantiation might happen somewhere else (navigation guards, router’s hooks etc…) but I’d like to demonstrate this as simple as possible.

In addition, we need to detect wether the module already exists and if it does, it makes totally sense to re-use it.

If you have a deep look at the code, we are registering the module only if it doesn’t exist, when it does, we are just re-using the instance we have with the preserved state (in case).

Main thing of this approach is the ease of having lots of component relying on the same module (re-instantiated per every component) and it ensures the consistency of data. Also, what I like, it’s basically the ability to leave the actions/mutations resolving themselves and its atomicity without worrying too much about the related component being mounted or unmounted.

I have also created an example with higher complexity:

If you want to have a look at the source code, feel free to check the following repo out: https://github.com/cdbkr/vue-dynamic-modules

--

--

Senior Front End Engineer @IBM — thoughts are my own / I’m a passionate developer and kitesurfer