Add Simple State Management with Vuex

John Au-Yeung - Jan 26 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

With Vuex, we can store our Vue app’s state in a central location.

In this article, we’ll look at how to add Vuex to our app and add a simple store.

What is Vuex?

Vuex is for storing states that are needed by multiple components in one central location.

https://thewebdev.info/wp-content/uploads/2020/06/vuex.png

It lets us get and set shared state and propagate any changes made to the shared state automatically to all components.

In the workflow diagram above, we can see that Vuex mutations are committed by our code when we get something from the back end API.

The mutation will update the state with the back end API data and the state will be updated in our Vue components.

We can also dispatch mutations from Vue components to change the Vuex store state the change will be propagated to all components that have access to the store.

Getting Started

We can include Vuex with a script tag in our HTML code:

<script src="https://unpkg.com/vuex"></script>
Enter fullscreen mode Exit fullscreen mode

Then we can create a simple store by using the Vuex.Store as follows:

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

The code above is a store which stores the state count .

Then we can commit the increase mutation by running:

store.commit("increase");
Enter fullscreen mode Exit fullscreen mode

Then we can get the state after the mutation is done by running:

console.log(store.state.count);
Enter fullscreen mode Exit fullscreen mode

store.state.count is the count state from the Vuex store. Then we should see 1 logged.

Getting Vuex State into Vue Components

We can get the state into our store by adding a computed property.

Therefore, to add the state into our store, we can write the following:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  computed: {  
    count() {  
      return store.state.count;  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <p>{{count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

Then we should see 0 displayed since count is 0 initially in the store .

Then whenever store.state.count updates, the computed property will be updated and the view will update with the new value.

A more convenient way to inject the store into all child components is to add the store in the root component as follows:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store  
});
Enter fullscreen mode Exit fullscreen mode

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <p>{{$store.state.count}}</p>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

Then it’ll be available to all child components and we don’t have to worry about add computed properties for every value.

It works with child component without much changes:

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

Vue.component("counter", {  
  template: `<div>{{ count }}</div>`,  
  computed: {  
    count() {  
      return this.$store.state.count;  
    }  
  }  
});

new Vue({  
  el: "#app",  
  store  
});
Enter fullscreen mode Exit fullscreen mode

this.$store is available to the counter component just by including it in the root Vue component.

The mapState Helper

To avoid adding a new computed property for every state that’s in the store, we can use the mapState helper to add it. For example, we can write the following code to do that:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

Vue.component("counter", {  
  data() {  
    return {  
      localCount: 1  
    };  
  },  
  template: `  
    <div>  
      <div>{{ count }}</div>  
      <div>{{ countAlias }}</div>  
      <div>{{ countPlusLocal }}</div>  
    </div>  
  `,  
  computed: Vuex.mapState({  
    count: state => state.count,  
    countAlias: "count",  
    countPlusLocal(state) {  
      return state.count + this.localCount;  
    }  
  })  
});

new Vue({  
  el: "#app",  
  store  
});
Enter fullscreen mode Exit fullscreen mode

index.html :

<!DOCTYPE html>  
<html>  
  <head>  
    <title>App</title>  
    <meta charset="UTF-8" />  
    <script src="https://unpkg.com/vue/dist/vue.js"></script>  
    <script src="https://unpkg.com/vuex"></script>  
  </head>  
  <body>  
    <div id="app">  
      <counter></counter>  
    </div>  
    <script src="index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

Then we see:

001
Enter fullscreen mode Exit fullscreen mode

Because we called mapState as follows:

computed: Vuex.mapState({  
  count: state => state.count,  
  countAlias: "count",  
  countPlusLocal(state) {  
    return state.count + this.localCount;  
  }  
})
Enter fullscreen mode Exit fullscreen mode

We have:

count: state => state.count,
Enter fullscreen mode Exit fullscreen mode

which gets the count state from the store and returns it. The count is 0 so we get 0.

Then we have:

countAlias: "count"
Enter fullscreen mode Exit fullscreen mode

which is a shorthand for:

count: state => state.count
Enter fullscreen mode Exit fullscreen mode

And finally, we have:

countPlusLocal(state) {  
  return state.count + this.localCount;  
}
Enter fullscreen mode Exit fullscreen mode

which adds state.count from the store to this.localCount , which we set to 1.

Object Spread Operator

We can combine local computed properties with mapState by applying the spread operator to mapState as follows:

index.js :

const store = new Vuex.Store({  
  state: {  
    count: 0  
  },  
  mutations: {  
    increase(state) {  
      state.count++;  
    }  
  }  
});

Vue.component("counter", {  
  data() {  
    return {  
      localCount: 1  
    };  
  },  
  template: `  
    <div>  
      <div>{{ count }}</div>  
      <div>{{ foo }}</div>        
    </div>  
  `,  
  computed: {  
    foo() {  
      return 2;  
    },  
    ...Vuex.mapState({  
      count: "count"  
    })  
  }  
});

new Vue({  
  el: "#app",  
  store  
});
Enter fullscreen mode Exit fullscreen mode

Then we get:

02
Enter fullscreen mode Exit fullscreen mode

displayed since foo always returns 2.

Components Can Still Have Local State

Components can still have their own local state, so we don’t have to put everything in the Vuex store.

Conclusion

We can add a Vuex store to our app to store the states of our app that are shared by multiple components.

To make getting state easy, we can include the store in the root Vue component.

Then we call the mapState helper to get the states we want in any component.

We can also combine it with the local states with the spread operator in the computed object.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .