Welcome folks today in this tutorial we will be using Vuex State Management Library in Vue.js to Make a simple Todo CRUD App from scratch
. This will be a tutorial for pure beginners which are very new to Vuex
as a library to manage state or data inside Vue Applications. For this application we will be using JSONPlaceholder Fake REST API
. A step by step youtube video is also shown below.
LIVE DEMO
Advantages of Vuex
- Vuex is a lightweight and easy to understand state management library similiar to
redux
which is used in React applications - It is helpful in scenarios where you have multiple components or modules running at the same time and you want to manage your state or data effectively without compromising the application.
Installation
First of all you will create your brand new vue.js project at the command line with the help of this command shown below
vue create testproject
Now we will install the vuex
library inside our project like this
Now we need to open our App.vue
file and write down the following code into it we will be creating three components namely AddTodo
and Todos
and FilterTods
components to add a Todo and display all Todos and filter todos to only display a certain number of todos in application respectively.
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<template> <div id="app"> <AddTodo /> <FilterTodos/> <Todos /> </div> </template> <script> import AddTodo from './components/AddTodo.vue' import Todos from './components/Todos.vue' import FilterTodos from './components/FilterTodos.vue' export default { name: 'App', components: { AddTodo, Todos, FilterTodos } } </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style> |
Now inside our components folder you need to create these components like this as shown in the figure
Todos.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<template> <div> <div> <h1>Todos</h1> </div> </div> </template> <script> export default { name:"Todos" } </script> <style scoped> </style> |
AddTodo.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<template> <div> <div> <h1>AddTodo</h1> </div> </div> </template> <script> export default { name:"AddTodo" } </script> <style scoped> </style> |
FilterTodos.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<template> <div> <div> <h1>FilterTodos</h1> </div> </div> </template> <script> export default { name:"FilterTodos" } </script> <style scoped> </style> |
If you now run your vue.js server by executing the following commad given below
npm run serve
You will see the following output
as shown below
Configuring Vuex Library
Now we will configure the Vuex
library inside Vue application. So now first of all go to src
directory of your project and make a new folder store
inside it as show below
Now make another file inside the store folder called as index.js
which will import the Vue
and Vuex
library like this as shown below
store/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Entrypoint for vuex import Vuex from 'vuex'; import Vue from 'vue'; import todos from './modules/todos'; // Load vuex Vue.use(Vuex); // Create store export default new Vuex.Store({ modules: { todos } }); |
In this block of code we are importing a module called as todos
from the modules folder which we haven’t created. We will create this modules
folder and inside it we will create this todos
module later. So after it we are invoking the Store
method of Vuex library to import this todos module.
Registering Vuex to Vue App
Now to register this Vuex library we need to go to the main.js
file of the Vue Application and copy paste the following code into it
main.js
1 2 3 4 5 6 7 8 9 10 |
import Vue from 'vue'; import App from './App.vue'; import store from './store'; Vue.config.productionTip = false new Vue({ store, render: h => h(App), }).$mount('#app') |
Here we are just importing the store file inside this file and passing this store to the Vue
main constructor.
Now we need to create the modules folder inside the store
folder that we have created earlier like this
So we have created a file todos.js
inside the modules folder. Inside this file we will write all the code regarding our state and vuex
Installing Axios
For this application we will making a HTTP requests to JSONPLACEHOLDER
API to get our todos update our todos and also remove the todos so for that we need to use this library. Go to command prompt and execute the command given below
npm install --save axios
Main Components of Vuex
Largely there are four main components of Vuex
where the whole library concept revolves. These are as follows
-
State
: This is the actualstate
ordata
that you define inside your application Getter
: This is the second component which is responsible for getting the value of the state or getting the data of the application at any time. You can call getter function to get the state of the applicationAction
: Action from the name itself takes the appropriateaction
towards the state of the application for example manipulate or change the value of the state by some business logic of the application and then it contains a special argument inside it’s function called ascommit
which we need to call when we are done changing the value. This commit will execute lastly to call mutation.Mutation
: Mutation is actually responsible for actually taking the modified state or data fromaction
throughcommit
and actually changing the original state of the application.
So now inside your todos.js
we will define the state and action and getter and mutation like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import axios from 'axios'; const state = { todos: [] }; const getters = { allTodos: (state) => { return state.todos } }; const actions = { async fetchTodos({ commit }) { const response = await axios.get('https://jsonplaceholder.typicode.com/todos'); commit('setTodos', response.data); } } const mutations = { setTodos: (state, todos) => (state.todos = todos) }; export default { state, getters, actions, mutations }; |
Here there are three methods are there of getter,action and mutation namely getAllTodos
and fetchTodos
and setTodos
respectively.
And we have defined a single property in the state called a empty array of todos. And also inside the action method fetchTodos we are using axios
to make a http request to JSONPLACEHOLDER
API to return all todos and then setting the result to the todos array and passing those changes to commit ultimately calling mutation function setTodos to change the original state of the todos.
Lastly we are exporting all these methods by using the export
keyword so that we can use these methods inside our application.
Now go to your Todos.vue
component and we will be using these functions to render out all the todos which will be coming from the api
Todos.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<template> <div> <div> <h1>Todos</h1> <div class="todos"> <div v-for="todo in allTodos" class="todo" v-bind:key="todo.key" > {{ todo.title }} </div> </div> </div> </div> </template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<script> import { mapGetters, mapActions } from 'vuex'; export default { name: "Todos", methods:{ ...mapActions(["fetchTodos"]) }, computed: mapGetters(["allTodos"]), created() { this.fetchTodos(); } }; </script> |
First of all we have imported the methods mapGetters and mapActions which are library methods using destructuring from vuex
and then we have called these inside the methods section. We are using the computed
property to get the state or data in this case the array of todos. And also there is a special function inside every component in Vue which is created()
which will automatically be called once the component is created and inside it we are calling the fetchTodos
fuction to get all the todos and set the state of todos array to the result.
And then inside the template we are using v-for
directive to loop through the array displaying all todos also passing the id which will be unique through key
parameter and then inside it printing the title of the todo.
If you run the app your todos will be there like this as shown below.
We can add some styles to it so copy paste the css code inside the style tags like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<style scoped> .todos { display: grid; grid-template-columns: repeat(3, 1fr); grid-gap: 1rem; } .todo { border: 1px solid #ccc; background: #41b883; padding: 1rem; border-radius: 5px; text-align: center; position: relative; cursor: pointer; } i { position: absolute; bottom: 10px; right: 10px; color: #fff; cursor: pointer; } .legend { display: flex; justify-content: space-around; margin-bottom: 1rem; } .complete-box, .incomplete-box { display: inline-block; width: 10px; height: 10px; } .complete-box { background: #35495e; } .incomplete-box { background: #41b883; } .is-complete { color: #fff; background: #35495e; } @media (max-width: 500px) { .todos { grid-template-columns: 1fr; } } </style> |
Deleting Todos
Now we will be deleting Todos using a delete button which will be present on each todo which is rendered. Now inside the todos.vue
you need to add this delete icon in the template of the component
1 2 3 4 5 6 7 8 |
<div v-for="todo in allTodos" class="todo" v-bind:key="todo.key" > {{ todo.title }} <i class="fas fa-trash-alt" v-on:click="deleteTodo(todo.id)"></i> </div> |
Here we are using fontawesome library so you need to include the cdn of fontawesome inside your public/index.html
file like this
1 |
<script src="https://kit.fontawesome.com/dfc3b70b57.js" crossorigin="anonymous"></script> |
Now if you open the application you will see the following output like this
So we have defined a custom function on this fontawesome icon by the help of vue directive v-on:click
. We will define this deleteTodo function inside the script section like this
After defining this method inside this array we need to define this method inside our module file which is todos.js
todos.js
1 2 3 4 5 6 7 8 9 10 |
const actions = { async deleteTodo({ commit }, id) { await axios.delete(`https://jsonplaceholder.typicode.com/todos/${id}`); commit('removeTodo', id); } } |
1 2 3 4 5 |
const mutations = { removeTodo:(state,id) => state.todos = state.todos.filter((todo) => todo.id !== id) } |
So now if you click on the dustbin icon on the todo that todo will be deleted from the todos array inside the application.
Updating Todos
Now we will be adding a dynamic class on the todo once we double click on the todo. Through this we will be updating the todo.
Now go to Todos.vue
file and change the template as this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<template> <div> <h3>Todos</h3> <div class="legend"> <span>Double click to mark as complete</span> <span> <span class="incomplete-box"></span> = Incomplete </span> <span> <span class="complete-box"></span> = Complete </span> </div> <div class="todos"> <div @dblclick="onDblClick(todo)" :key="todo.id" v-for="todo in allTodos" class="todo" v-bind:class="{'is-complete':todo.completed}" > {{ todo.title }} <i class="fas fa-trash-alt" v-on:click="deleteTodo(todo.id)"></i> </div> </div> </div> </template> |
Now as you can see when we load todos from the api the completed values for the todos are random some are true and some are false and depending on that value we are toggling the class that we have provided to the todo through v-bind:class
directive
Also we have binded a double click directive on the todo through @dblclick
and we have binded a custom method and passing the id of the todo which was clicked.
So now we will define this function in the next step
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<script> import { mapGetters, mapActions } from 'vuex'; export default { name: "Todos", methods:{ ...mapActions(["fetchTodos","deleteTodo","updateTodo"]), onDblClick(todo) { const updatedTodo = { id: todo.id, title: todo.title, completed: !todo.completed } this.updateTodo(updatedTodo); } }, computed: mapGetters(["allTodos"]), created() { this.fetchTodos(); } }; </script> |
So now inside this method we are constructing a new todo passing all the three properties id of the todo title of the todo and also the completed value inside this we are toggling this value. Let’s say the value is true we are changing to false and vice versa. After that we are calling another method which will actually update the data for us which is updateTodo
now we have also added this method inside the array as you can see.
Now we need to simply define this method inside the todos.js
file
1 2 3 4 5 6 7 8 9 10 |
const actions = { async updateTodo({ commit }, updatedTodo) { const response = await axios.put(`https://jsonplaceholder.typicode.com/todos/${updatedTodo.id}`, updatedTodo); commit('updateTodo', response.data); } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
const mutations = { updateTodo: (state, updatedTodo) => { // Find index const index = state.todos.findIndex(todo => todo.id === updatedTodo.id); if (index !== -1) { state.todos.splice(index, 1, updatedTodo); } } } |
So now in this case we are making a put request to api and then the updated result is returned and after that we are passing that result to the mutation function to modify the state or update the state. For updating the state we are using the splice method of javascript.
So now if you double click the todo color will change from green to blue
Adding Todos
Lastly we take the operation of adding todos inside our array through a simple form. So go to AddTodo.vue
component and copy paste the following code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
<template> <div> <h3>Add Todo</h3> <div class="add"> <form @submit="onSubmit"> <input type="text" v-model="title" placeholder="Add Todo" /> <input type="submit" value="Submit"> </form> </div> </div> </template> <script> import { mapActions } from 'vuex'; export default { name: "AddTodo", data() { return { title: '' } }, methods: { ...mapActions(["addTodo"]), onSubmit(event) { event.preventDefault(); this.addTodo(this.title); this.title = ''; } } } </script> <style scoped> form { display: flex; } input[type="text"] { flex: 10; padding: 10px; border: 1px solid #41b883; outline: 0; } input[type="submit"] { flex: 2; background: #41b883; color: #fff; border: 1px solid #41b883; cursor: pointer; } </style> |
So this is a fairly simple component inside the template we have a simple form and also we have attached a submit directive of Vue when the form submits we are calling a custom function and inside the fields we have a single field for the title of the todo and then the submit button. We have provided some custom styling to the form.
Now in the script section we are again importing all the method of vuex and then calling the method addTodo
So we need to go to the todos.js
file and define this method like this
1 2 3 4 5 6 7 8 9 10 |
const actions = { async addTodo({commit},title){ const response = await axios.post(`https://jsonplaceholder.typicode.com/todos`, {title:title,completed:false}) commit('addTodo',response.data) } } |
1 2 3 4 5 6 |
const mutations = { addTodo:(state,newTodo) => state.todos.unshift(newTodo) } |
So now if you go to the form submit a title and enter you will see your todo appeared on the screen
Filtering Todos on Length
Now lastly we only want to display x
number of todos inside our application through the api. For this you need to open your FilterTodos.vue
file and copy paste the following code which is given below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<template> <div> Filter Todos: <select v-on:change="filterTodos($event)"> <option value="200">200</option> <option value="100">100</option> <option value="50">50</option> <option value="20">20</option> <option value="10">10</option> <option value="5">5</option> </select> </div> </template> <script> import { mapActions } from 'vuex'; export default { name: "FilterTodos", methods: { ...mapActions(["filterTodos"]) } } </script> <style scoped> select { margin-top: 20px; padding: 6px; border: 1px solid #41b883; } </style> |
As you can see we have a simple select box where we can select how many todos we want to display on the application. For this select box inside the template we have binded a directive of Vue to get the value of the select box when it is changed and also we have defined a custom function and we have passed this function to our todos.js
file. Now we will go to this file and define this function like this
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const actions = { async filterTodos({ commit }, event) { // Get the limit const limit = parseInt(event.target.options[event.target.options.selectedIndex].innerText); // Request const response = await axios.get(`https://jsonplaceholder.typicode.com/todos?_limit=${limit}`); commit('setTodos', response.data); }, } |
Now you can see we have selected 10 todos in the select box and only 10 todos are displayed and fetched from the rest api.
This was the application guys. Hope you like it please share this post with your friends. The github repo of this project is given below.
DOWNLOAD SOURCE CODE
Live Demo