In this tutorial, we will learn about the new VueJS library called Vuex and it's core concepts along with an use case where Vuex is useful and how you can use it.
VueJS is a library for building interactive web interfaces. It provides data-reactive components with a simple and flexible API. Many developers use Vue because it is lightweight and easy to wrap your head around.
So where’s the problem? Well, developers often need to alter a parent data/property from a child component. In Vue, we might need to use an accessor function such as this.$parent.data
. We could also keep emitting events from the child component to the parent component.
While this might be a simple way to alter a parent data, it becomes stressful as the code base increases. What happens if the data gets used across several components? If we have 30 components it would mean us emitting 30 different events. Not only does this mean more chance for error, we would need to spend more time debugging and we end up repeating similar code 30 times.
Now we have identified our problem, how should we solve it?
Answer: Vuex. Vuex makes it possible to use a centralized state management in your application. Vuex is a library which helps you to enforce a Flux-like application architecture in your Vue application.
What is Vuex?
According to the official documentation for Vuex, it is a state management pattern and library for VueJS applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only get mutated in a predictable fashion.
In Vuex, the object created is called a store. Before going further, let us acquaint ourselves with some core concepts:
Vuex uses a single state tree. This single object contains all your application-level state and serves as the “single source of truth”. This also means you will have only one store for each application. A single state tree makes it straightforward to locate a specific piece of state and allows us to take snapshots of the current app state for debugging purposes.
What we will build
While we are talking about Vuex and Vue js, it will be beneficial if we write a simple app that implements Vuex for its state management. To achieve this, we will be building a simple Todo List
.
Below is a picture of what you will build:
Setting up the app
To get started, and also skip the process of configuring webpack for the compilation from ES6 to ES5, we will use the vue cli
. If you do not have the Vue cli installed, install it by doing the following:
1sudo npm install -g vue-cli
After installing the vue-cli, it is now time for us to create a Vue project. To do that, we run the following command:
Note: For this tutorial, when running the command below, choose ‘no’ when asked to use Vue Router
, as we will not be needing it.
1vue init webpack todo
After running the above code, we will need to change directory into our application, and install the required modules:
1//change directory to the folder 2 cd todo 3 //install all required npm modules 4 npm install
To install Vuex, we run the following command:
1npm install vuex
Once Vuex is installed, let’s explore our state
, actions
, mutations
and getters
for our sample application.
State:
1state: { 2 todos: []; 3 }
As mentioned earlier, the state is a JavaScript object containing the data we want to use in our store. In our example, our state is an object that consists of one key named todos
. The todos
key is an empty array which will hold all the todo items we have.
Actions:
1actions: { 2 ADD_TODO: function({ commit }, new_todo) { 3 var set_new = { 4 todo: new_todo, 5 status: false 6 }; 7 commit("ADD_TODO_MUTATION", set_new); 8 }, 9 COMPLETE_TODO: function({ commit }, todo) { 10 commit("COMPLETE_TODO_MUTATION", todo); 11 } 12 }
In our Actions
section, we define two functions:
ADD_TODO
: This function creates a new todo object with two keys : todo and status. The status key is set to ‘false’ by default as we want to use this field to check if a todo is complete or not. If the status field is true, then it means the todo item is complete. This function further commits a mutation called ADD_TODO_MUTATION
with the new todo object it has created.COMPLETE_TODO
: This function sets a todo item to complete. It triggers a mutation called COMPLETE_TODO_MUTATION
which in turn mutates the state of the todo object and sets it to ‘true’.1mutations: { 2 ADD_TODO_MUTATION: function(state, new_todo) { 3 state.todos.push(new_todo); 4 }, 5 COMPLETE_TODO_MUTATION: function(state, todo) { 6 state.todos.find(x => x.todo === todo).status = true; 7 } 8 }
In the above code, we have our mutations, which directly alter the state of our store. Taking a closer look at the code, we notice two functions, which are:
ADD_TODO_MUTATION
: This function pushes a new todo object to the array of todos
we have in our store. Notice that the todo object added to our todos
array is the exact todo that is being passed from our ADD_TODO
action.COMPLETE_TODO_MUTATION
: This function receives a todo name, then looks for the exact todo object and sets its status to ‘true’. This implies that the selected todo is complete.1getters: { 2 not_done: state => { 3 var filtered = state.todos.filter(function(el) { 4 return el.status === false; 5 }); 6 return filtered; 7 }, 8 done: state => { 9 var filtered = state.todos.filter(function(el) { 10 return el.status === true; 11 }); 12 return filtered; 13 } 14 }
In our getters
block, we define two functions:
not_done
: This is a helper function which filters all our todos
whose status is set to ‘false’.done
: This is a helper function which filters all our todos
whose status is set to true.Now that we have a working knowledge of what our state, actions, mutations, and getters are, let us bring them all together and create our Vuex store.
Let’s replace our store.js
with the following content:
1// src/store.js 2 3 import Vue from "vue"; 4 import Vuex from "vuex"; 5 Vue.use(Vuex); 6 const store = new Vuex.Store({ 7 state: { 8 todos: [] 9 }, 10 actions: { 11 ADD_TODO: function({ commit }, new_todo) { 12 var set_new = { 13 todo: new_todo, 14 status: false 15 }; 16 commit("ADD_TODO_MUTATION", set_new); 17 }, 18 COMPLETE_TODO: function({ commit }, todo) { 19 commit("COMPLETE_TODO_MUTATION", todo); 20 } 21 }, 22 mutations: { 23 ADD_TODO_MUTATION: function(state, new_todo) { 24 state.todos.push(new_todo); 25 }, 26 COMPLETE_TODO_MUTATION: function(state, todo) { 27 state.todos.find(x => x.todo === todo).status = true; 28 } 29 }, 30 getters: { 31 not_done: state => { 32 var filtered = state.todos.filter(function(el) { 33 return el.status === false; 34 }); 35 return filtered; 36 }, 37 done: state => { 38 var filtered = state.todos.filter(function(el) { 39 return el.status === true; 40 }); 41 return filtered; 42 } 43 } 44 }); 45 46 export default store;
In the above code, we will notice that in the actions
block, we have two functions. The first one commits the ADD_TODO_MUTATION
, which pushes a new todo
to the array of todos
, while the second function commits the COMPLETE_TODO_MUTATION
, which marks a todo
as completed. These functions do not alter the state of our store.
If you remember, we said mutations are committed
, this explains why we have the commit
parameter passed to the actions by default.
Next, we move to the mutations
block. Here, we also have two functions that alter the state of our store, and as such, the state parameter gets passed on to the functions.
We move to the getters
block, where we define two helper functions, which we use to get completed `todos’, and those that are not.
Now, we have our store set up, but we are yet to see it work with our application or even integrate with our current application. Let’s move further and integrate the store.
To notify Vue about our central store, we need to pass in the store object to our Vue instance while creating it. To do that, Let us replace the content of our src/main.js
with the following:
1// src/main.js 2 // The Vue build version to load with the `import` command 3 with an alias. 4 import Vue from 'vue' 5 import App from './App' 6 import store from './store' 7 Vue.config.productionTip = false 8 9 /* eslint-disable no-new */ 10 new Vue({ 11 el: '#app', 12 template: '<App/>', 13 store, 14 components: { App } 15 })
In the above code block, we will notice the import of our store, and also that we pass the store into the object while initializing it. I am pretty sure you know that we used ES2016 syntax and used store
as shorts for store: store
.
Now, Vue is aware of our store, and it is now accessible as this.$store
.
Next, Let us edit our hello world component, and make it more of a todo
app.
Let us open up our src\components\Hello.vue
file and replace its content with the following:
1<template> 2 3 <div class="todolist not-done"> 4 <h1>Todos</h1> 5 <input type="text" v-model="holder" class="form-control add-todo" placeholder="Add todo"> 6 <button id="checkAll" class="btn btn-success" :disabled="holder==''" @click="add_todo">Add Todo</button> 7 8 <hr> 9 <ul id="sortable" class="list-unstyled" v-if="not_done_todos"> 10 <li class="ui-state-default" v-for="todo in not_done_todos"> 11 <div class="checkbox"> 12 <label> 13 <input type="checkbox" @click="done_todo(todo.todo)" :value="todo.todo" :checked="todo.status" />{{todo.todo}}</label> 14 </div> 15 </li> 16 </ul> 17 <div class="todo-footer" v-if="not_done_todos"> 18 <strong><span class="count-todos">{{not_done_todos.length}}</span></strong> Item(s) Left 19 </div> 20 </div> 21 </template> 22 23 <script> 24 export default { 25 name: 'hello', 26 data () { 27 return { 28 holder: '' 29 } 30 }, 31 methods: { 32 add_todo: function(){ 33 this.$store.dispatch('ADD_TODO', this.holder); 34 this.holder = ''; 35 }, 36 done_todo: function(todo){ 37 this.$store.dispatch('COMPLETE_TODO', todo); 38 } 39 }, 40 computed: { 41 not_done_todos: function(){ 42 return this.$store.getters.not_done; 43 } 44 } 45 } 46 </script>
In the code block above, you will notice that we have declared some set of HTML elements, in which we can find:
We are now done reviewing the HTML part of our component.
Let us review our methods
and computed
properties, so we can see how we interact with Vuex.
Within our methods, we will notice the presence of two functions which are:
add_todo
: This method dispatches the current todo
item to the action called ADD_TODO
. This action then commits the ADD_TODO_MUTATION
.done_todo
: This method notifies our store about a completed todo item. By dispatching the action called COMPLETE_TODO
with the name of the completed todo item, the store gets notified. This, in turn, commits the COMPLETE_TODO_MUTATION
.Moving into our computed properties, we will notice that we have only one function called not_done_todos
. This function returns all the todos in our store which are not marked as complete. This is done by accessing what we call the getters
. (If you remember, we defined some getters
in our store which return filtered versions of our todos
)
The reason why we access the getters as a computed property is so that our component can get the current state once the state gets altered.
Next, let us create another component to display a list of all the completed todos
.
Let us create a new component called Done.vue
. We’ll copy the following content into it:
1<template> 2 <div class="done"> 3 <div class="todolist"> 4 <h1>Already Done</h1> 5 <ul id="done-items" class="list-unstyled" v-if="done_todos"> 6 <li v-for="todo in done_todos">{{todo.todo}}</li> 7 8 </ul> 9 </div> 10 </div> 11 </template> 12 <script> 13 export default { 14 computed: { 15 done_todos: function() { 16 return this.$store.getters.done; 17 } 18 } 19 } 20 </script>
In the above code block, we have created a list that shows all completed todos. By accessing a getter called done_todos
, we get a filtered list of all completed todos in our store.
So far, we have created two components which both interact with the same store.
Now we will open up our App.vue
file, and replace its content with the following:
1<template> 2 <div id="app"> 3 <div class="container"> 4 <div class="row"> 5 <div class="col-md-6"> 6 <hello></hello> 7 </div> 8 <div class="col-md-6"> 9 <done></done> 10 </div> 11 </div> 12 </div> 13 </div> 14 </template> 15 16 <script> 17 import Hello from './components/Hello' 18 import Done from './components/Done' 19 20 export default { 21 name: 'app', 22 components: { 23 Hello, 24 Done 25 } 26 } 27 </script>
In the code above, we have imported the two components which we have used in the course of this exercise. Also, we have displayed the components side by side, so we can see how they work with the Vuex store.
Below is an image of what we have built:
In the course of this tutorial, we have covered what Vuex is, a use case scenario where Vuex might be useful, and how to use Vuex.
We have also learned about the core concepts of Vuex such as state, actions, mutations, and getter.
The codebase to this guide can be found here. Feel free to download and play around with the code.