Skip to content

How to split Vuex Store into modules

If youve been following along in this series of Vue tutorials on how to make use of Vuex and Vue-Router in a Vue Project and how we can implement Firebase authentication in a Vue Project .

Even in such a simple project our Vuex Store and our Router files are growing starting to grow in size. Just imagine what those the size of those files would be in a larger more complex project. Pretty soon, we would end up with a file that is difficult to maintain, would require endless scrolling to find a section we would be interested in.

If you're working as part of a large team with a few developers and you're all working on different features of the application, but all sharing the same Vuex-Store file, you are going to run into continual merge conflict errors, which despite best efforts always seem to resort to issues being deployed to production.

Large code files also make unit testing a somewhat laborious process. Often with confusing unit tests being developed or in extreme cases large unit test files resembling the file under test.

What are Vuex-Store Modules

The solution is break down the Store.js into a multiple smaller module files. We are going to examine why the concept of code isolation is safer, cleaner and provides a clearer and easier understanding of program and data flow within an application.

The Vuex documentation, provides general information and it's quite easy to create name spaced modules, use the exposed helpers to connect the modules with created components and dynamically register them.

What this essentially means is that Vuex allows us to divide our store into modules. So in this example we are going to take our existing code which we have developed in Firebase authentication in a Vue Project split our Store.js file into modules.

Each module can contain its own state, getters, mutations, actions and even other nested modules. They are useful to separate areas of your app from other areas of your app.

After we've created the necessary modules, we register them in the store.

Vuex Modules can simply be thought of as Vuex stores within other Vuex stores.

We are also going to examine some additional file organisation techniques and strategies we can use in order to introduce concepts from strongly typed languages into JavaScript code in order to help reduce errors introduced by over use of Magic Strings and numbers.

Why Use Vuex-Store Modules

There are a number of reasons why you would want to make use of Vuex-Store modules besides the previous mentions of organisation, clean code, test-ability and maintainability. These are great reasons and definitely are always my first considerations. However, probably the best reason is code reuse.

If we take our example we're going to be dealing with in this tutorial Authentication using Firebase this is going to be a recurring theme in any project. You also probably don't want to re-implement this pattern and strategy from scratch every time you start a new project you will also obviously have a similar implementation using AWS Amplify.

Taking the opportunity to Modularize code enables you to reuse modules across several projects. So implement an approach in one project and it works you probably want to re-use across all your projects.

This further promotes collaboration across teams and projects and really promotes refining and improving your code bases in general.

How to create Vuex Modules

In order to start this process we will need to create a new folder in our source code directory which we will call store

Shell

We then create a blank index.js file in store directory and each of the subsequent child directories we created.

Shell

We are going to sightly refactor code for the project. Our existing store.js we developed in How to Use Vuex and Vue-Router in a Vue.JS Project .

We are now going to simplify in order to introduce Modularization, we will add the following code to index.js. You will notice we will still be making use of Vuex-Persist and LocalForage, these may be project specific settings that you will want to use, so we will set this up within our project specific Vuex Store configuration.

JS

We have drastically simplified this file now and any developer who opens this can quickly at a glance get to understand what the code conveys.

We are now ready to start developing our Authentication Module which in this example is going to focused around making use of Firebase Authentication.

We will create useful folder structure to help organise our code. We're going to try think of a good name for our folder.

Choosing good names takes time but saves more than it takes

Robert C. Martin - Clean Code
Shell

You will notice in both instances I created an empty JavaScript file and have named index.js . I have done this for a very explicit reason and that is to make use of syntactic sugar exposed by Webpack and other file bundlers in the JavaScript ecosystem and it has one of the most subtle but useful benefits of this organization structure. In ES6,

Having an index.js file in a folder lets you perform an import from the folder implicitly without specifying the index.js in the import statement.

We are also going to take this opportunity to to implement an approach in order to reduce the use of Magic Strings in our code. We are going to implement a types file which will contain constants we will use to define action and mutation names.

Shell

We will add our common names for our actions and mutations in the file as constants.

JS

We can now add some additional folders to separate our sections of store. The approach we are taking is just one of several possible approaches. I have chosen to take this approach as it suitable for the needs of this module and for the future growth of the additional complexity we will introduce at the later stage.

Shell

We can now refactor our store and move the code to the appropriate files. Lets move our actions first. Add the following code to src/store/auth/actions/index.js

The code almost remains the same just with some notable exceptions, we import our types file with Action and Mutation defined, then in the highlighted line we make use of the these labels using the [] notation refering to the action name.

You will notice we also make use of String Interpolation to to refer to the Mutation, using the back tick </code> and referring to our constants in our mutation commits i.e. <code>${[Mutation.LOGIN]}</code> </p> <!-- /wp:paragraph --> <!-- wp:advanced-gutenberg-blocks/code {"language":"js","source":"import firebase from 'firebase/app';\nimport 'firebase/auth';\nimport {Action, Mutation} from '../types'\n\nexport default {\n [Action.LOGIN]({commit}, authData) {\n return new Promise((resolve, reject) =\u003e {\n firebase.auth().signInWithEmailAndPassword(authData.email, authData.password)\n .then(() =\u003e {\n firebase.auth().onAuthStateChanged(authUser =\u003e {\n commit(${[Mutation.LOGIN]}, authUser);\n\n resolve()\n });\n })\n .catch(err =\u003e reject(err.message))\n }\n )\n },\n [Action.REGISTER]({commit}, user) {\n return new Promise((resolve, reject) =\u003e {\n firebase\n .auth()\n .createUserWithEmailAndPassword(user.email, user.password)\n .then(() =\u003e {\n firebase.auth().onAuthStateChanged(authUser =\u003e {\n commit(${[Mutation.LOGIN]}, authUser);\n resolve()\n });\n })\n .catch(err =\u003e {\n reject(err.message)\n });\n })\n },\n [Action.LOGOUT]({commit}) {\n firebase.auth().signOut().then(() =\u003e {\n firebase.auth().onAuthStateChanged(authUser =\u003e {\n commit(${[Mutation.LOGOUT]}, authUser);\n });\n }\n );\n },\n}","showLines":false,"highlightStart":"6","highlightEnd":"6"} /--> <!-- wp:paragraph --> <p>Lets add code to our <code>src/store/auth/getters/index.js</code></p> <!-- /wp:paragraph --> <!-- wp:advanced-gutenberg-blocks/code {"language":"js","source":"export default {\n\n loggedIn: state =\u003e !!state.user\n}","showLines":false} /--> <!-- wp:paragraph --> <p>Then our touch <code>src/store/auth/mutations/index.js</code></p> <!-- /wp:paragraph --> <!-- wp:advanced-gutenberg-blocks/code {"language":"js","source":"import {Mutation} from \u0022../types\u0022;\n\nexport default {\n\n [Mutation.LOGIN](state, payload) {\n state.user = payload.email;\n state.userId = payload.uid;\n },\n [Mutation.LOGOUT](state) {\n state.user = '';\n state.userId = ''\n }\n\n}\n","showLines":false} /--> <!-- wp:paragraph --> <p>and finally <code>src/store/auth/state/index.js</code></p> <!-- /wp:paragraph --> <!-- wp:advanced-gutenberg-blocks/code {"language":"js","source":"export default {\n state: {\n userId: null,\n user: null\n }\n}","showLines":false} /--> <!-- wp:paragraph --> <p>We can add the following code to our <code>src/store/auth/index.js</code></p> <!-- /wp:paragraph --> <!-- wp:advanced-gutenberg-blocks/code {"language":"js","source":"import state from './state';\nimport getters from './getters';\nimport mutations from './mutations';\nimport actions from './actions';\n\nexport default {\n state,\n getters,\n mutations,\n actions\n}\n","showLines":false} /--> <!-- wp:paragraph --> <p>Our new modularized store is ready for use. However, we just need to make a few minor edits to our Views to incorporate our changes.</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>We will only take a look at our Login and Logout functionality but the principles now apply wherever we make use of the Authentication store in our application.</p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>We now import our types file and use string interpolation to refer to action we want to call. This reduces making use of Magic Strings in our code. i.e <code> this.$store.dispatch(${Action.LOGIN}, {email: this.email, password: this.password})</code> </p> <!-- /wp:paragraph --> <!-- wp:paragraph --> <p>You will notice I make use of the <a rel="noreferrer noopener" aria-label="Material Design Component Framework (opens in a new tab)" href="https://threenine.co.uk/material-component-framework-vue/" target="_blank">Material Design Component Framework</a> to assist in developing the Presentation views of my code. For interest, my framework of choice for this is  <a rel="noreferrer noopener" href="https://mdbootstrap.com/products/vue-ui-kit/?utm_ref_id=47167" target="_blank">MDB Vue : A Material Design Component for Vue Pro</a></p> <!-- /wp:paragraph --> <!-- wp:advanced-gutenberg-blocks/code {"language":"js","source":"\u003ctemplate\u003e\n \u003cdiv class=\u0022mt-5 p-5\u0022\u003e\n \u003cmdb-card class=\u0022mt-5 p-5\u0022\u003e\n \u003cmdb-card-body \u003e\n \u003cform novalidate @submit.prevent=\u0022login\u0022\u003e\n \u003cp class=\u0022h4 text-center mb-4\u0022\u003eSign in\u003c/p\u003e\n \u003cmdb-list-group v-if=\u0022errors.length\u0022 class=\u0022mb-4\u0022\u003e\n \u003cmdb-list-group-item v-for=\u0022error in errors\u0022 :key=\u0022error\u0022 class=\u0022list-group-item-danger\u0022\u003e{{error}}\u003c/mdb-list-group-item\u003e\n \u003c/mdb-list-group\u003e\n\n \u003cdiv class=\u0022grey-text mt-5\u0022\u003e\n \u003cmdb-input v-model=\u0022email\u0022 label=\u0022Email\u0022 icon=\u0022envelope\u0022 type=\u0022email\u0022 required /\u003e\n \u003cmdb-input v-model=\u0022password\u0022 label=\u0022Password\u0022 icon=\u0022lock\u0022 type=\u0022password\u0022 required /\u003e\n \u003c/div\u003e\n \u003cdiv class=\u0022text-center\u0022\u003e\n \u003cmdb-btn type=\u0022submit\u0022\u003eLogin\u003c/mdb-btn\u003e\n \u003c/div\u003e\n \u003crouter-link to=\u0022register\u0022\u003eRegister\u003c/router-link\u003e\n \u003c/form\u003e\n \u003c/mdb-card-body\u003e\n \u003c/mdb-card\u003e\n \u003c/div\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\n import {mdbBtn, mdbCard, mdbCardBody, mdbInput, mdbListGroup, mdbListGroupItem} from 'mdbvue';\n\n import validate from '../../modules/validation-module'\n import {Action} from '../../store/auth/types'\n\n\n export default {\n name: 'login',\n components: {\n mdbCard,\n mdbCardBody,\n mdbBtn,\n mdbInput,\n mdbListGroup,\n mdbListGroupItem\n },\n data() {\n return {\n email: '',\n password: '',\n errors: []\n }\n },\n methods: {\n login(event) {\n this.errors = [];\n if (!validate.email(this.email)) {\n this.errors.push(\u0022Please enter a valid email address\u0022)\n }\n if (!this.errors.length) {\n event.target.classList.add('was-validated');\n this.$store.dispatch(${Action.LOGIN}`, {email: this.email, password: this.password})\n .then(()=\u003e this.$router.push(\u0022/dashboard\u0022))\n .catch(() =\u003e this.errors.push(\u0022Login unsuccessful\u0022));\n }\n }\n }\n }\n\u003c/script\u003e\n","showLines":false,"highlightStart":"29","highlightEnd":"29"} /-->

Summary

We have broken our store down to make use of a Modular approach instead of creating one large un-maintainable file.

The approach outlined is just one of the approaches you can use, however it is my preferred approach if you are going to work on large complex projects, helping to isolate your store logic to specific modules.

Gary Woodfine
Latest posts by Gary Woodfine (see all)