17 Jan 2024 · Software Engineering

    Managing State With Vuex in Vue.js

    12 min read
    Contents

    Managing state while building web applications can be very challenging when working on medium to large-scale applications. State management is an important part of Vue applications as it ensures scalability and maintainability of code, so we need to understand it properly. In this article, we will see how we can easily manage the state using a popular and powerful Vue.js library- Vuex (now Pinia). The official state management library for Vue is now changed to Pinia but don’t worry Vuex is still maintained by the team.

    Understanding the need for State Management

    In the vast landscape of front-end development, the challenge of managing application state becomes increasingly complex. With the help of a centralized state management concept, it simplifies state handling by keeping it in a single, easily accessible location.

    Before moving further into this article, we need to first understand what is state management.

    What is State Management?

    State management is a process of managing and sharing data within a Vue.js application. keeping the data secure, easily accessible becomes difficult when working with growing or large scale applications.

    Introduction to Vuex

    Vuex is an official state management library for managing the state in Vue.js applications. The library is somewhat similar to Redux wherein the data flows in one direction, with actions and mutations modifying data in a single source of truth called the store. In simple terms, vuex uses the Flux architecture where you can store, retrieve, and update data easily.

    Vuex is a self-contained app that has four components. We will next discuss these components in detail.

    Components of Vuex:

    • State: It is the single source of truth for your application means it contains all your application level states.
    • Mutations: These are the functions that help in modifying the state and ensuring all changes are tracked.
    • Actions: These are the like mutations but they commit the mutations.
    • Getters: These are the computed properties for stores that allow the derivation of state values.

    Now, you have a complete understanding of what is state management, what is vuex, etc. So, now let’s start and see how to integrate vuex into the Vue application.

    Setting Up Vuex in a Vue.js Project:

    To begin with, we need to set up a Vue.js project first. Here’s how you can get started:

    Step 1: Install Vue CLI

    To create a new Vue project first, install the Vue CLI by running the following command in your terminal:

    npm install -g @vue/cli

    Step 2: Creating a new Vue project

    Once the CLI is installed, create a new Vue project by running the following command:

    vue create project-name

    and follow all the prompts after running the above command:

    Vue CLI v5.0.8
    
    ✔ Please pick a preset: Default ([Vue 3] babel, eslint)
    ...

    Step 3: Navigate to your project

    After the project is created, navigate to your project directory:

    cd project-name

    Step 4: Install Vuex in Vue.js

    Next, install the Vuex in Vue by running the following command:

    npm install vuex

    or

    yarn add vuex

    Now you have successfully created a new Vue.js project along with the Vuex library. To see the output, run the following command in your terminal:

    npm run serve

    Open your web browser and visit localhost:8080 to see the following output:

    Now that your example vue application is set up, we will start working on using vuex.

    Real-World Application: Building a Todo List with Vuex

    To have a better understanding, we will work on a To-Do application using vuex and learn everything about its components i.e., using the store, getters, mutations, and actions.

    Creating a Todo component

    Start by defining a component in the vue project. To create a new component, navigate to the src/components and create a new file called MyTodoForm.vue. Open it and add the following code:

    //MyTodoForm.vue
    
    <template>
      <form class="myForm" @submit="addTodo">
        <input name="taskTodo" class="formInput" v-model="taskTodo" placeholder="Add a new todo..." required />
        <button class="formBtn" type="submit">Add</button>
      </form>
    </template>
    
    <script>
    export default {
      data() {
        return {
          taskTodo: '',
        };
      },
      methods: {
        addTodo: function (e) {
          e.preventDefault();
          this.$store.commit('addTodo', this.taskTodo);
          this.taskTodo = '';
        },
      },
    };
    </script>

    The above form has an HTML template that contains an input field and a button that allows the users to add new to-do items. On submitting, the form triggers the addTodo method by preventing the default behavior.

    In the <script>, we have initialized a taskTodo property as an empty string. Also, the addTodo method commits a Vuex mutation (addTodo) with the current taskTodo value, and then resets taskTodo to an empty string once the user submits a new todo.

    Now let’s add some styles to the component. Copy the following styles and paste them after the </script>:

    <style scoped>
    .myForm {
      margin-bottom: 30px;
    }
    
    .formInput {
      width: 30em;
      padding: 20px;
      margin-right: 8px;
      border: 2px solid #1393d4;
      border-radius: 1em;
      font-size: 15px;
    }
    
    .formBtn {
      width: 10em;
      padding: 20px;
      margin-right: 8px;
      border: 2px solid #1393d4;
      border-radius: 1em;
      color: white;
      background-color: rgb(44, 122, 223);
      cursor: pointer;
      font-size: 15px;
    }
    </style>
      

    Now create another file named MyTodoList.vue in the components directory and add the given code:

    // MyTodoList.vue
    
    <template>
        <div>
            <div class="todo-computed-list">
                <div class="todo-computed-item" v-for="todo in mytodos" :key="todo.id">
                    <p :style="{ 'text-decoration': todo.completed ? 'line-through' : 'none' }">{{ todo.title }}</p>
                    <input type="checkbox"  class="check-box" name="todo.id" v-on:change="updateTodo(todo)" v-bind:checked="todo.completed" />
                </div>
                <div>
                    <h4 class="todo-completed">You have completed: {{ todoComplete }} tasks.</h4>
                </div>
            </div>
        </div>
    </template>
      
    <script>
    export default {
        computed: {
            mytodos() {
                return this.$store.state.myTodoList;
            },
            todoComplete() {
                return this.$store.getters.finalTodosLength;
            },
        },
        methods: {
            updateTodo: function (todo) {
                this.$store.commit('updateTodo', todo.id);
            },
            submitTodo: function (event) {
                event.preventDefault();
                this.$store.commit('addTodo', this.todo);
                this.todo = '';
            },
        },
    };
    </script>

    The above component will render the dynamic and reactive display of todos in the form of a list. The user can click the checkbox to mark it as done or completed.

    In the <script> part, we have defined two computed properties: mytodos retrieves the todo list from the Vuex state, and todoComplete fetches the count of completed todos using a Vuex getter. The updateTodo method commits a mutation (updateTodo) to toggle the completion status of a todo.

    The submitTodo method commits a mutation that adds a new to-do item to the state.

    Now let’s add some more styles to the component. Copy the following styles and paste them after the </script>:

    <style scoped>
    .todo-computed-list {
        margin-top: 20px;
    }
    
    .todo-computed-item {
        width: 40em;
        margin-bottom: 10px;
        padding: 8px;
        border: 2px solid #03344c;
        border-radius: 0.5em;
        font-size: 20px;
        color: #1393d4;
        display: flex;
        justify-content: space-between;
    }
    
    .check-box {
        width: 2em;
        border: 1px solid #1393d4;
    }
    
    .todo-computed-item p {
        margin: 0;
    }
    
    .todo-completed {
        font-size: 20px;
        text-align: center;
    }
    </style>

    To see the working of these components, we have to render them to our Vue application. Update the App.vue in your srcdirectory as follows:

    //App.vue
    
    <template>
      <div id="app" class="container">
        <h1>To-Do Application using Vue.js</h1>
        <MyTodoForm /> // using MyTodoForm component
        <MyTodoList /> // using MyTodoList component
      </div>
    </template>
    <script>
    
    import MyTodoList from './components/MyTodoList';
    import MyTodoForm from './components/MyTodoForm';
    
    export default {
      name: 'App',
      components: {
        MyTodoList, // renders the MyTodoList component
        MyTodoForm, // renders the MyTodoForm component
      },
    };
    </script>
    
    <style>
    .container {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    </style>
    

    Now we have successfully created the UI for our To-do application. At this point, if you try to run the app, it will serve a blank page with an error:

    Uncaught TypeError: Cannot read properties of undefined (reading 'state')
        at Proxy.mytodos (MyTodoList.vue:35:1)

    The above error indicates that we are accessing the state property of an undefined object. But don’t worry, we will solve this error next!

    Understanding State and Mutations

    Define the app’s State

    In a Vue.js application, the state represents the data that drives the application. As we are working on a Todo List application, we can define an array of todos.

    To start with, navigate to src of your project and create a file called store.js. Open it and define the initial state as given below:

    // store.js
    
    import Vuex from 'vuex';
    
    export const store = new Vuex.Store({
        state: {
          myTodoList: [],
        },
        mutations: {
          ...
        },
        actions: {
          ...
        },
        getters: {
          ...
        },
    });
    
    
    export default store;

    Here, we’ve created a store with an initial state object. Our state at the moment contains an array called myTodoList which will be responsible for storing all the Todo’s in our app.

    Adding Mutations

    Mutations are functions that modify the state. They are crucial to ensure that state changes are explicit and tracked.

    To add the mutations, go back to your store.js and add the below mutation property after the state created earlier:

    // store.js
    export const store = new Vuex.Store({
        state: {
            myTodoList: [],
        },
        mutations: {
            addTodo(state, todo) {
                state.myTodoList = [
                    ...state.myTodoList,
                    {
                        id: Math.random(),
                        title: todo,
                        completed: false,
                    },
                ];
            },
            updateTodo(state, todoId) {
                state.myTodoList = state.myTodoList.map((item) => {
                    if (item.id === todoId) {
                        item.completed = !item.completed;
                    }
                    return item;
                });
            },
        },
      // ...
    });
    

    Above we have defined mutations for adding and updating todos using which we can create and update the to-do state in the application. Here, we have created two mutations called:

    • addTodo: This mutation will add a single to-do item.
    • updateTodo: This mutation will update the to-do item status.

    Asynchronous Operations with Actions

    We have already learned about mutations. While mutations are synchronous, actions are used for asynchronous operations. As we are only adding the user-created to-do items in the application, we will not be using the actions property.

    These actions are used to perform asynchronous operations or complex logic before committing mutations. If in the future you find the need to perform asynchronous operations like fetching data from an API before updating the state, you can introduce actions.

    For example, if you want to fetch some data from an API, you may add the below code in your store.js file:

    // store.js
    
    actions: {
      async fetchData({ commit }) {
        try {
          const res = await fetch('https://api.websitename.com/data');
          const myData = await res.json();
          commit('updateData', myData);
        } catch (error) {
          console.error('Error:', error);
        }
      },
    },

    Adding Getters

    Getters are computed properties that allow the derivation of state values. For instance, you might want to get the count of completed todos.

    To define a getter, go back to your store.js and add the following getters property after actions:

    // store.js
    export default new Vuex.Store({
      state: {
        myTodoList: [],
      },
      mutations: {
        // ... (same as above)
      },
      actions: {
        // No actions for to-do applications...
      },
      getters: {
        finalTodosLength: (state) => {
            const myCompleteTodos = state.myTodoList.filter((item) => item.completed);
            return myCompleteTodos.length;
        },
      },
    });

    Finally bringing everything together, you will see the store.js file as follows:

    import Vuex from 'vuex';
    
    export const store = new Vuex.Store({
        state: {
            myTodoList: [],
        },
        mutations: {
            addTodo(state, todo) {
                state.myTodoList = [
                    ...state.myTodoList,
                    {
                        id: Math.random(),
                        title: todo,
                        completed: false,
                    },
                ];
            },
            updateTodo(state, todoId) {
                state.myTodoList = state.myTodoList.map((item) => {
                    if (item.id === todoId) {
                        item.completed = !item.completed;
                    }
                    return item;
                });
            },
        },
        actions: {
            //No actions for to-do applications...
        },
        getters: {
            finalTodosLength: (state) => {
                const myCompleteTodos = state.myTodoList.filter((item) => item.completed);
                return myCompleteTodos.length;
            },
        },
    });
    
    
    export default store;

    The final step to successfully add the vuex and access the components into our to-do application is to connect the store.

    To do that just open the main.js file and update it with the following code:

    // main.js 
    
    import { createApp } from 'vue';
    import App from './App.vue';
    import store from './store'; //change the location as per your project directory (if needed)
    
    const app = createApp(App);
    
    app.use(store);
    
    app.mount('#app');

    Run the Application

    Now you have successfully completed your to-do application and learned the usage of Vuex in the Vue.js project. Here is the complete source code link on GitHub.

    To see the output for the above To-do application, run the local development server using the npm run serve command in your terminal:

    npm run serve

    This will start a server on your localhost, usually at port 8080. Open your web browser of choice and visit localhost:8080 to see the following:

    Best Practices for Effective State Management

    The following are a few practices for better state management in your Vue applications so that you can get the best results possible:

    • Keep State Immutable: Try not to manipulate the state directly instead use mutations for changes.
    • Use Actions Wisely: Reserve actions for asynchronous operations, keeping mutations for synchronous changes.
    • Think Modular: For better management, split your store into modules based on your application functionality.

    Do checkout these awesome projects that uses Vuex for state management like HackernewsRealWorld, and there are more you can checkout on GitHub.

    Conclusion

    In this article, we learned to manage the state with vuex in Vue.js. Vuex is a powerful tool for vue.js developers as it provides a clear and efficient way to manage the state of the applications. The clear separation of concerns provided by the components of Vuex i.e., state, mutations, actions, and getters makes code more maintainable, opening up possibilities for building robust and feature-rich web applications.

    One thought on “Managing State With Vuex in Vue.js

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    I am freelance Technical Writer with a portfolio of over 400 technical articles/blogs, worked with different clients. I was also given the title of Geek of the Month award and Bronze level Technical Writer by GeeksforGeeks.
    Avatar
    Reviewed by:
    I picked up most of my soft/hardware troubleshooting skills in the US Army. A decade of Java development drove me to operations, scaling infrastructure to cope with the thundering herd. Engineering coach and CTO of Teleclinic.