Vuex: Why We Need SPA State Management
Tags: vue, webdev, tutorial, javascript
Published: 2020-03-12 17:57:47

One of the most beautiful things about Vue.js is the relative simplicity it brings to modern web development, building Single Page Applications has never been easier. JavaScript frameworks like Vue came with component based design patterns. Whole web applications are just a collection of individual pieces (components) sharing data, the bigger the application gets, the harder it is for data to remain consistent and be managed in each individual component. This data is commonly referred to as application state. For Vue.js, Vuex is the most widely used state management library, today we’ll go into adding and integrating Vuex into a Vue.js applications.

Not only does Vuex work as a central store for your application state, it sets rules to ensure data is changed in a  way that is expected. Vuex ensures your views remain consistent with your application data. Don't worry if this doesn't make sense now, it'll all come together as we go on and build something.

As an semi-regular conference and event goer, I tend to meet a bunch of people, during our interactions I agree to do certain stuff that I most certainly always forget. So we’re going to build something literally no one else but me will use - a reminder (glorified to-do list) app.  

Before we dive into it, here’s a few things you’ll need:

- Basic knowledge of Vue.js
- Node.js and Yarn installed

Alright!! We’ve already covered what Vuex does and why it does it. We need to setup our project, open your terminal and type  `vue create <project-name>` to do so, you’d need the Vue CLI installed. If you don’t have that installed you can get it [here](https://cli.vuejs.org/). Select the default project setup and once everything is done and we have our project initialized, run `cd <project-name>` and  `yarn serve`. You should see your usual vue starter page

![](https://paper-attachments.dropbox.com/s_4F1FD7F04BB95598A64F1B607ED9E431FD572BA9B92348BDFCD1019BA9C4EF5B_1580726446305_image.png)


After getting this running, we need to add vuex to our project. In your terminal, type `vue add vuex` and after, you should see your directory structure change quite a bit. As with most state management tools, Vuex has a central store/single state tree to store application state, ours is in the src folder, you get a **store.js** file or a store folder with an index.js file. If not you can create them and paste the following code in

```
    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    export default new Vuex.Store({
      state: {
      },
      mutations: {
      },
      actions: {
      }
    })
```

You will also see a change in **src/main.js**,  as we import the store. If not, paste the following code, 
```
    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')
```
At this point looking at store, you’re probably wondering what all the sections are for. We’ll briefly go over them before we can go deeper.

**State:** Application state is the data your application uses. 

**Mutations:** Synchronous method of changing store state and directly commit to change state.

**Actions:** Commit mutations and give way for for asynchronous operations.

*bonus*
 
Getters:  Computed properties derived from store state.

Don’t worry if it doesn’t all make sense now, we’ll get into building to make it easier. We’ve just added Vuex to our project, now we have to test it. We’ll start out by defining some data for our store. In your store, we’ll define a new data property called username by pasting `username: "danielphiri"`  into the state portion of our store. We want to make this show on our webpage, in **HelloWorld.vue**, clear the <template> tag and paste the following
```
    <template>
      <div>
        <h1> {{ username }} </h1>
      </div>
    </template>
```
In the `<script>` section of the same file, we need to add `import mapState from 'vuex'` and paste the following 
```
    computed: {
        ...mapState(["username"])
      }
```
We should then see the value we kept in our store displayed on the screen.


![](https://paper-attachments.dropbox.com/s_4F1FD7F04BB95598A64F1B607ED9E431FD572BA9B92348BDFCD1019BA9C4EF5B_1580732209771_image.png)


Now getting into the core of the reminder app we want to build, we will need to be able to input task details and who we need to perform them for. We should also be able to dismiss all tasks or individual tasks.  We need to conceptualize a data model for the state so we know what data we’re using up in the HTML portion of our application. In your store,  paste the following code

```
    state: {
        username: "danielphiri",
        tasks: [
          { taskName: "take pictures", taskReciever: "mom and dad" },
          { taskName: "email organisers slides", taskReciever: "myself" },
          { taskName: "send resume", taskReciever: "dev job" },
        ]
      },
      mutations: {
        ADD_TASK: (state, task) => {
        
          state.tasks.push(task);
        },
        REMOVE_TASK: (state, task) => {
          state.tasks.splice(task, 1);
        },
      actions: {
        removeTask: (context, task) => {
          context.commit("REMOVE_TASK", task);
        },
      }
```

In our state, we define a username and an array that holds our tasks and related data.  We define two mutations, `ADD_TASK` which changes the state by adding a task to the tasks array, and `REMOVE_TASK` that removes a task from the tasks array. Lastly we define an action `removeTask` that gives the option a remove tasks asynchronously with some custom logic. You will notice the `context` object as the first argument in `removeTask`, actions in vuex use `context` which gives them access to store properties and methods like `context.commit()` which it used to commit a mutation.

To get started we’ll create a component to allow us to input tasks and display them as well as remove them. Let’s call this **Main.vue** and we’ll paste the following code in the `<script>` section:

> Don’t forget to add your Main component to your **App.vue** file.

```
    <script>
    import { mapState, mapMutations, mapActions } from "vuex";
    export default {
      name: "Main",
      data() {
        return {
          taskName: "",
          taskReciever: "",
        };
      },
      computed: {
        ...mapState(["tasks", "username"])
      },
      methods: {
        ...mapMutations(["ADD_TASK"]),
        ...mapActions(["removeTask"]),
        addTask: function() {
          let newTask = Object.create(null);
          newTask["taskName"] = this.taskName;
          newTask["taskReciever"] = this.taskReciever;
          this.ADD_TASK(newTask);
          this.taskReciever = "";
          this.taskName = "";
        },
        removeTasks: function(task) {
          this.removeTask(task);
        }
      }
    };
    </script>
```
At the top of the file, you will notice that we import a couple of helper functions. They’re all pretty similar in functionality, `mapState` for example helps us map store state to local (component) computed properties. So `mapMutations` does the same for store mutations and `mapActions` for store Actions.  We go on to use `mapState` to enable us display “username" and “tasks" in our component. We also use `mapMutations` in the methods property so we can call store mutations as functions with parameters as we did when we defined `addTask()`  which we use to perform mutations while passing the newTask object as a parameter. 

In the <template> section of our Main.vue, we paste the following code
```
    <template>
      <div class="home">
        <div class="hello center">
          <div >
            <h1 class="header-text"> Hi 👋, {{ username }}</h1>
            <h3 class="header-text"> Add a few tasks</h3>
            <form @submit.prevent="addTask">
              <input class="input" type="text" placeholder="I'm supposed to.." v-model="taskName" />
              <input class="input" type="text" placeholder="for this person..." v-model="taskReciever" />
              <button class="add-button" type="submit" placeholder="Add task to list">Add task to list</button>
            </form>
            <ul>
              <li v-for="(task, index) in tasks" v-bind:key="index">
                {{ task.taskName }} for {{task.taskReciever}}
                <button
                  v-on:click="removeTasks(index)"class="remove">Done ✅</button>
              </li>
            </ul>
          </div>
          <div class></div>
        </div>
      </div>
    </template>
```
We can directly interpolate our username from store because we mapped it as a computed property using `mapState`, the same goes for the tasks. We use `v-for` to loop over the tasks array from our store and display all our tasks their properties i.e `taskName` and `taskReciever` . We also use a form to mutate tasks to our store. On submit (`@submit`) a.k.a when we press the button after filling in tasks, we call the `addTask` method which then changes our state by adding whatever we input to the tasks array. Optionally you can add a style section by pasting this
```
    <style>
    html,
    #app,
    .home {
      height: 100%;
    }
    body {
      background-color: #050505;
      margin: 0;
      height: 100%;
    }
    input {
      border: none;
      padding: 5%;
      width: calc(100% - 40px);
      box-shadow: 0 3px 3px lightgrey;
      margin-bottom: 5%;
      outline: none;
    }
    .header-text {
      color: #e9e9e9;
    }
    .add-button {
      border: none;
      border-radius: 2px;
      padding: 5%;
      background-color: #0cf50cbb;
      box-shadow: 0 2px 2px #fff;
      width: calc(100% - 100px);
      margin-bottom: 2%;
      outline: none;
    }
    .main {
      display: grid;
      grid-template-columns: repeat(2, 50%);
      grid-template-rows: 100%;
      height: 100%;
    }
    .center {
      display: flex;
      justify-content: center;
    }
    .left,
    .right {
      padding: 30px;
    }
    ul {
      list-style-type: none;
      padding: 0;
    }
    ul li {
      padding: 4%;
      background: white;
      margin-bottom: 8px;
      border-radius: 5px;
    }
    .right {
      grid-area: right;
      background-color: #e9e9e9;
    }
    .remove {
      float: right;
      text-transform: uppercase;
      font-size: 0.8em;
      background: #050505;
      border: none;
        border-radius: 5px;
      padding: 5px;
      color: #00ff88de;
      cursor: pointer;
    }
    </style>
```
Save your work and run it you should see this.

![](https://paper-attachments.dropbox.com/s_4F1FD7F04BB95598A64F1B607ED9E431FD572BA9B92348BDFCD1019BA9C4EF5B_1580812890485_image.png)


Right now, we have some basic vuex operations working but you can’t really tell why we use vuex, we’re only using a single component. Let’s create another compenent called **Stats.vue**, we’ll use this to display a few stats and show how vuex actions can be properly put to use. 

For starters, we want to be able to display the number of pending tasks we have, in our store we can define a getter to do this by pasting the following text below the state object, 
```
    getters: {
        taskCount: state => {
          return state.tasks.length;
        }
      },
```
We then add another mutation to the store
```
    REMOVE_ALL: state => {
          state.tasks = [];
        },
```
This lets us clear every task in our list. Finally in our state, we add another action to the store right below `removeTask` by adding the following code.
```
    removeAll({ commit }) {
          return new Promise((resolve) => {
            setTimeout(() => {
              commit("REMOVE_ALL");
              resolve();
            }, 2000);
          });
        }
```
You notice we define a promise and use `setTimeout` function to add a bit of a delay (2 seconds) before we commit our `REMOVE_ALL` mutation. Thus the asynchronous nature of vuex actions. We’re free to play around with the logic that dictates how we perform actions, this could be used in a shopping cart, trading website, chat application - it has so many uses.

Back to our **Stats.vue** file, we paste the following in the `<script>` section
```
    <script>
    import { mapGetters, mapActions, mapMutations, mapState } from 'vuex'
    export default {
      name: 'Stats',
      computed: {
        ...mapGetters(['taskCount']),
        ...mapState(["username"])
      },
      data() {
        return {
          message: ""
        }
      },
      methods: {
        ...mapMutations(['REMOVE_ALL']),
        ...mapActions(['removeAll']),
        removeAllTasks() {
          this.removeAll().then(() => {
            this.message = 'Self care - tasks are gone'
          });
        }
      }
    }
    </script>
```
In **Stats.vue**, like we said, we wanted to be able to count how many tasks we have pending. We use the `mapGetters` helper to display that computed property.  In methods, we initialize our `removeAll` action and `REMOVE_ALL` mutations as well as define `removeAllTasks` which remember, has a promise and lets us use the `then()` prototype to display text once the promise is fulfilled.

In the `<template>` section of **Stats.vue**, paste the following code
```
    <template>
      <div class="stats">
        <h3 class="header-text">Here are your numbers, {{username}} 😬 </h3>
        <p class="header-text">You need to perform {{ taskCount }} tasks fam</p>
        <button class="" v-on:click="removeAllTasks">Nope, can't even..</button>
        <p class="header-text">{{ message }}</p>
      </div>
    </template>
```
Here we have a button to remove all the tasks and a message that gets displayed when our promise is fulfilled. 

Run your app and you should have a pretty nifty web app like this

![](https://paper-attachments.dropbox.com/s_4F1FD7F04BB95598A64F1B607ED9E431FD572BA9B92348BDFCD1019BA9C4EF5B_1580819445271_vuex-app.gif)



## Conclusion

We went over why we need Vuex, Vuex operations and helpers and built an app using it. We have a functional web app that you can test out [here](https://eloquent-archimedes-bc8b6f.netlify.com/), we say how we can use Vuex to manipulate a single data source and avoid inconsistencies. We built a multi component app and shared data between them, 

Should you want to dive deeper in the topic I recommend the following resources.

- [Vuex Documentation](https://vuex.vuejs.org/)
- [WTF is Vuex? A Beginner's Guide To Vue's Application Data Store](https://vuejsdevelopers.com/2017/05/15/vue-js-what-is-vuex/)
- [Architecting Vuex store for large-scale Vue.js applications](https://dev.to/localeai/architecting-vuex-store-for-large-scale-vue-js-applications-4f1f)
- [Managing State with Vuex - the Guide I Wish I'd Had](https://dev.to/decoeur_/managing-state-with-vuex---the-guide-i-wish-id-had-28h)

Check out the full version this on [GitHub](https://github.com/malgamves/vuex-remindrr-app) or [CodeSandbox](https://codesandbox.io/s/vuex-remindrr-app-cvf9u). I hope you enjoyed this and if you have any questions or want to say hi, feel free to [tweet](https://twitter.com/malgamves) at me. Till next time.