How To Use Vuex to Store State in Vue.js

How To Use Vuex to Store State in Vue.js

  • 340

How To Use Vuex to Store State in Vue.js .We will use Vuex for storing the central state of our application. It is the most popular state management library for Vue.

Storing data in a central location is crucial for complex front-end apps. Otherwise, data is spread everywhere and has to be passed between components.

This is especially important if you use frameworks like React, Angular, or Vue.js. These three frameworks divide apps into components and you have to pass data between them if you want to share data in an ad-hoc fashion.

React only has one-way binding between components, so you can only pass data down the component tree. In any case, if you have lots of data to pass around, it gets confusing very quickly.

In this piece, we will write a little game where the app gets your current location and shows you cat pictures. The app will ask you questions and you can go back and forth by click Yes and No buttons.

The data store will track which page you’re at so that you can move forward and backward.

We will use Vuex for storing the central state of our application. It is the most popular state management library for Vue.js apps with support from the Vue.js developers themselves.

Getting Started

To start, we need to install the Vue CLI to make building our app easier. It contains a development server and scripts to generate boilerplate code.

Run npm install -g @vue/cli to install it globally.

Then, we run vue create geokittyjs to scaffold our app.

After that, we can run the development server to display our app by running npm run serve. It will refresh the browser automatically as we are updating our code.

We install our supporting libraries by running npm i yuex vue-material vue-router superagent.

  • Vue Material makes our app look pretty.
  • Vuex is the state management library.
  • Vue Router is the routing library for routing URLs to our page components.
  • SuperAgent is the HTTP client that we will use.

Coding

Now we are ready to code.

First, we add Material icons to index.html to get pretty icons, like so:

<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:400,500,700,400italic|Material+Icons">

Then, we write the logic part of the app. We start with some essential constants. We put them all in src/constants.js for easy access.

In there, add:

let IPURL = 'https://api.ipify.org?format=json';
let LATLONGURL = 'https://api.ipgeolocation.io/ipgeo';
let IPKEY = 'get key from https://ipgeolocation.io/'
let MASHAPEKEY = 'get key from Mashape'
let GOOGLEAPIKEY = 'get key from Google';
let CONTINENTS = {
    "AD": "Europe",
    "AE": "Asia",
    "AF": "Asia",
    "AG": "North America",
    "AI": "North America",
    "AL": "Europe",
    "AM": "Asia",
    "AN": "North America",
    "AO": "Africa",
    "AQ": "Antarctica",
    "AR": "South America",
    "AS": "Australia",
    "AT": "Europe",
    "AU": "Australia",
    "AW": "North America",
    "AZ": "Asia",
    "BA": "Europe",
    "BB": "North America",
    "BD": "Asia",
    "BE": "Europe",
    "BF": "Africa",
    "BG": "Europe",
    "BH": "Asia",
    "BI": "Africa",
    "BJ": "Africa",
    "BM": "North America",
    "BN": "Asia",
    "BO": "South America",
    "BR": "South America",
    "BS": "North America",
    "BT": "Asia",
    "BW": "Africa",
    "BY": "Europe",
    "BZ": "North America",
    "CA": "North America",
    "CC": "Asia",
    "CD": "Africa",
    "CF": "Africa",
    "CG": "Africa",
    "CH": "Europe",
    "CI": "Africa",
    "CK": "Australia",
    "CL": "South America",
    "CM": "Africa",
    "CN": "Asia",
    "CO": "South America",
    "CR": "North America",
    "CU": "North America",
    "CV": "Africa",
    "CX": "Asia",
    "CY": "Asia",
    "CZ": "Europe",
    "DE": "Europe",
    "DJ": "Africa",
    "DK": "Europe",
    "DM": "North America",
    "DO": "North America",
    "DZ": "Africa",
    "EC": "South America",
    "EE": "Europe",
    "EG": "Africa",
    "EH": "Africa",
    "ER": "Africa",
    "ES": "Europe",
    "ET": "Africa",
    "FI": "Europe",
    "FJ": "Australia",
    "FK": "South America",
    "FM": "Australia",
    "FO": "Europe",
    "FR": "Europe",
    "GA": "Africa",
    "GB": "Europe",
    "GD": "North America",
    "GE": "Asia",
    "GF": "South America",
    "GG": "Europe",
    "GH": "Africa",
    "GI": "Europe",
    "GL": "North America",
    "GM": "Africa",
    "GN": "Africa",
    "GP": "North America",
    "GQ": "Africa",
    "GR": "Europe",
    "GS": "Antarctica",
    "GT": "North America",
    "GU": "Australia",
    "GW": "Africa",
    "GY": "South America",
    "HK": "Asia",
    "HN": "North America",
    "HR": "Europe",
    "HT": "North America",
    "HU": "Europe",
    "ID": "Asia",
    "IE": "Europe",
    "IL": "Asia",
    "IM": "Europe",
    "IN": "Asia",
    "IO": "Asia",
    "IQ": "Asia",
    "IR": "Asia",
    "IS": "Europe",
    "IT": "Europe",
    "JE": "Europe",
    "JM": "North America",
    "JO": "Asia",
    "JP": "Asia",
    "KE": "Africa",
    "KG": "Asia",
    "KH": "Asia",
    "KI": "Australia",
    "KM": "Africa",
    "KN": "North America",
    "KP": "Asia",
    "KR": "Asia",
    "KW": "Asia",
    "KY": "North America",
    "KZ": "Asia",
    "LA": "Asia",
    "LB": "Asia",
    "LC": "North America",
    "LI": "Europe",
    "LK": "Asia",
    "LR": "Africa",
    "LS": "Africa",
    "LT": "Europe",
    "LU": "Europe",
    "LV": "Europe",
    "LY": "Africa",
    "MA": "Africa",
    "MC": "Europe",
    "MD": "Europe",
    "ME": "Europe",
    "MG": "Africa",
    "MH": "Australia",
    "MK": "Europe",
    "ML": "Africa",
    "MM": "Asia",
    "MN": "Asia",
    "MO": "Asia",
    "MP": "Australia",
    "MQ": "North America",
    "MR": "Africa",
    "MS": "North America",
    "MT": "Europe",
    "MU": "Africa",
    "MV": "Asia",
    "MW": "Africa",
    "MX": "North America",
    "MY": "Asia",
    "MZ": "Africa",
    "NA": "Africa",
    "NC": "Australia",
    "NE": "Africa",
    "NF": "Australia",
    "NG": "Africa",
    "NI": "North America",
    "NL": "Europe",
    "NO": "Europe",
    "NP": "Asia",
    "NR": "Australia",
    "NU": "Australia",
    "NZ": "Australia",
    "OM": "Asia",
    "PA": "North America",
    "PE": "South America",
    "PF": "Australia",
    "PG": "Australia",
    "PH": "Asia",
    "PK": "Asia",
    "PL": "Europe",
    "PM": "North America",
    "PN": "Australia",
    "PR": "North America",
    "PS": "Asia",
    "PT": "Europe",
    "PW": "Australia",
    "PY": "South America",
    "QA": "Asia",
    "RE": "Africa",
    "RO": "Europe",
    "RS": "Europe",
    "RU": "Europe",
    "RW": "Africa",
    "SA": "Asia",
    "SB": "Australia",
    "SC": "Africa",
    "SD": "Africa",
    "SE": "Europe",
    "SG": "Asia",
    "SH": "Africa",
    "SI": "Europe",
    "SJ": "Europe",
    "SK": "Europe",
    "SL": "Africa",
    "SM": "Europe",
    "SN": "Africa",
    "SO": "Africa",
    "SR": "South America",
    "ST": "Africa",
    "SV": "North America",
    "SY": "Asia",
    "SZ": "Africa",
    "TC": "North America",
    "TD": "Africa",
    "TF": "Antarctica",
    "TG": "Africa",
    "TH": "Asia",
    "TJ": "Asia",
    "TK": "Australia",
    "TM": "Asia",
    "TN": "Africa",
    "TO": "Australia",
    "TR": "Asia",
    "TT": "North America",
    "TV": "Australia",
    "TW": "Asia",
    "TZ": "Africa",
    "UA": "Europe",
    "UG": "Africa",
    "US": "North America",
    "UY": "South America",
    "UZ": "Asia",
    "VC": "North America",
    "VE": "South America",
    "VG": "North America",
    "VI": "North America",
    "VN": "Asia",
    "VU": "Australia",
    "WF": "Australia",
    "WS": "Australia",
    "YE": "Asia",
    "YT": "Africa",
    "ZA": "Africa",
    "ZM": "Africa",
    "ZW": "Africa"
}
export { IPURL, LATLONGURL, MASHAPEKEY, GOOGLEAPIKEY, CONTINENTS, IPKEY };

src.constants.js

We need them for our API calls later.

Then, we create the data store for storing our data in src/store/store.js:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++
        },
        reset(state) {
            state.count = 0;
        },
        disappoint(state) {
            state.count = 9;
        }
    },
    getters: {
        getCount: state => {
            return state.count
        }
    }
})
export default store;

Store.js

This will store the stage of the app that we are in so we know which page to go to. The code above is where the state management magic happens.

Now, we can create our components. We’ll create the pages that you will see.

In src/components, add a file called City.vue and put in the following code:

<template>
  <div class="hello">
    <md-content>
      <h1>Can your choice in kittens reveal where you live?</h1>
      <h1>You live in {{city}}.</h1>
      <md-button class="md-raised md-accent" @click="next()">Yes</md-button>
      <md-button class="md-raised md-accent" @click="disappoint()">No</md-button>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "City",
  data() {
    return {};
  },
  props: ["city"],
  methods: {
    next() {
      store.commit("increment");
    },
disappoint() {
      store.commit("disappoint");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

City.vue

Then, create a file called Continent.vue and add:

<template>
  <div class="hello">
    <md-content>
      <h1>Can your choice in kittens reveal where you live?</h1>
      <h1>Hmm, your first choice suggests you live in {{continent}}. Is that correct?
      </h1>
      <md-button class="md-raised md-accent" @click="next()">Yes</md-button>
      <md-button class="md-raised md-accent" @click="disappoint()">No</md-button>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "Continent",
  data() {
    return {};
  },
  props: ["continent"],
  methods: {
    next() {
      store.commit("increment");
    },
disappoint() {
      store.commit("disappoint");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

Continent.vue

Next, create a file called Country.vue and add:

<template>
  <div class="hello">
    <md-content>
      <h1>Can your choice in kittens reveal where you live?</h1>
      <h1>We're thinking {{country}}. Is that right?</h1>
      <md-button class="md-raised md-accent" @click="next()">Yes</md-button>
      <md-button class="md-raised md-accent" @click="disappoint()">No</md-button>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "Country",
  data() {
    return {};
  },
  props: ["country"],
  methods: {
    next() {
      store.commit("increment");
    },
disappoint() {
      store.commit("disappoint");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

Country.vue

Create a file called Disappoint.vue and add:

<template>
  <div class="hello">
    <md-content>
      <h1>Rawr! Sry 2 Disapoint.</h1>
      <md-button class="md-raised md-accent" @click="reset()">Try Again</md-button>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "Home",
  data() {
    return {};
  },
  methods: {
    reset() {
      store.commit("reset");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

Disappoint.vue

Then, create a file called Home.vue and:

<template>
  <div class="hello">
    <md-toolbar class="md-accent">
      <h3 class="md-title">GeoKitty</h3>
    </md-toolbar>
    <div v-if="continent && country && region">
      <where-continent v-if="count == 0"></where-continent>
      <continent v-if="count == 1" :continent="continent"></continent>
      <where-country v-if="count == 2"></where-country>
      <country v-if="count == 3" :country="country"></country>
      <where-region v-if="count == 4"></where-region>
      <region v-if="count == 5" :region="region"></region>
      <where-city v-if="count == 6"></where-city>
      <city v-if="count == 7" :city="city"></city>
      <win v-if="count == 8"></win>
      <disappoint v-if="count == 9"></disappoint>
    </div>
    <div v-else>
      <h1>Loading...</h1>
    </div>
  </div>
</template>
<script>
import store from "../store/store";
import {
  IPURL,
  LATLONGURL,
  MASHAPEKEY,
  GOOGLEAPIKEY,
  CONTINENTS
} from "../constants";
const request = require("superagent");
export default {
  name: "Home",
  data() {
    return {
      continent: "",
      country: "",
      region: "",
      city: ""
    };
  },
computed: {
    count() {
      return store.state.count;
    }
  },
methods: {
    getLocation() {
      request
        .get(IPURL)
        .then(res => {
          let ip = res.body.ip;
          return request
            .get(`${LATLONGURL}?apiKey=${IPKEY}&ip=${ip}`)
            .set("Content-Type", "application/json");
        })
        .then(res => {
          let result = JSON.parse(res.text);
          let lat = result.latitude;
          let long = result.longitude;
          this.country = result.country_name;
          this.region = result.state_prov;
          return request
            .get(`https://maps.googleapis.com/maps/api/geocode/json`)
            .query({ latlng: `${lat},${long}`, key: GOOGLEAPIKEY });
        })
        .then(res => {
          this.continent =
            CONTINENTS[res.body.results[0].address_components[6].short_name];
          this.city = res.body.results[0].address_components[3].long_name;
        })
        .catch(err => {});
    }
  },
beforeMount() {
    this.getLocation();
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-accent {
  background-color: #ff5252;
  background-color: var(--md-theme-demo-light-accent, #ff5252);
  color: white;
}
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

Home.vue

The logic of the code is as follows — we get your public IP address from the first API call, then, from that, it can get your location data.

In the template, we check for the state which is returned in the logic below, in the computed property:

count() {
  return store.state.count;
}

Whenever the state is updated, the count function will return the latest value. Whatever is updated automatically, you can put it in the computed property.

In Region.vue, which we create, add:

<template>
  <div class="hello">
    <md-content>
      <h1>Can your choice in kittens reveal where you live?</h1>
      <h1>{{region}}? Is that right?</h1>
      <md-button class="md-raised md-accent" @click="next()">Yes</md-button>
      <md-button class="md-raised md-accent" @click="disappoint()">No</md-button>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "Region",
  props: ["region"],
  data() {
    return {};
  },
  methods: {
    next() {
      store.commit("increment");
    },
disappoint() {
      store.commit("disappoint");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

Region.vue

The rest is created in a similar way.

We create WhereCity.vue, WhereContinent.vue, WhereCountry.vue, WhereRegion.vue, and Win.vue.

In WhereCIty.vue, we add:

<template>
  <div class="hello">
    <md-content>
      <h1>Can your choice in kittens reveal where you live?</h1>
      <h1>Finally, which of these two do you prefer?</h1>
      <md-card class="left">
        <md-card-media>
          <img src='../assets/7.jpg' alt="cat" @click="next()">
        </md-card-media>
      </md-card>
      <md-card class="right">
        <md-card-media>
          <img src='../assets/8.jpg' alt="cat" @click="next()">
        </md-card-media>
      </md-card>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "WhereCity",
  data() {
    return {};
  },
  methods: {
    next() {
      store.commit("increment");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

****WhereCIty.vue

In WhereContinent.vue, we add:

<template>
  <div class="hello">
    <md-content>
      <h1>Can your choice in kittens reveal where you live?</h1>
      <h1>Start by clicking your favorite kitten.</h1>
      <md-card class="left">
        <md-card-media>
          <img src='../assets/1.jpg' alt="cat" @click="next()">
        </md-card-media>
      </md-card>
      <md-card class="right" >
        <md-card-media>
          <img src='../assets/2.jpg' alt="cat" @click="next()">
        </md-card-media>
      </md-card>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "WhereContinent",
  data() {
    return {};
  },
  methods: {
    next() {
      store.commit("increment");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

WhereContinent.vue

In WhereCountry.vue, we add:

<template>
  <div class="hello">
    <md-content>
      <h1>Can your choice in kittens reveal where you live?</h1>
      <h1>Ok, now select between these cuties..</h1>
      <md-card class="left">
        <md-card-media>
          <img src='../assets/3.jpg' alt="cat" @click="next()">
        </md-card-media>
      </md-card>
      <md-card class="right">
        <md-card-media>
          <img src='../assets/4.jpg' alt="cat" @click="next()">
        </md-card-media>
      </md-card>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "WhereCountry",
  data() {
    return {};
  },
methods: {
    next() {
      store.commit("increment");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

WhereCountry.vue

In WhereRegion.vue, we add:

<template>
  <div class="hello">
    <md-content>
      <h1>Can your choice in kittens reveal where you live?</h1>
      <h1>Your favorite between these two?</h1>
      <md-card class="left">
        <md-card-media>
          <img src='../assets/5.jpg' alt="cat" @click="next()">
        </md-card-media>
      </md-card>
      <md-card class="right">
        <md-card-media>
          <img src='../assets/6.jpg' alt="cat" @click="next()">
        </md-card-media>
      </md-card>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "WhereRegion",
  data() {
    return {};
  },
  methods: {
    next() {
      store.commit("increment");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

WhereRegion.vue

And finally, in Win.vue, we add:

<template>
  <div class="hello">
    <md-content>
      <h1>Meow! Meez win again! </h1>
      <md-button class="md-raised md-accent" @click="reset()">Try Again</md-button>
    </md-content>
  </div>
</template>
<script>
import store from "../store/store";
export default {
  name: "Home",
  data() {
    return {};
  },
  methods: {
    reset() {
      store.commit("reset");
    }
  }
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.md-card {
  width: 48vw;
  margin: 4px;
  vertical-align: top;
}
</style>

Win.vue

Note that, in each file, we have a call for store.commit. This is where the state is updated.

In all files, if there are images referenced in the templates, you can add cat pictures with the same name to the folder referenced in the template.

Then, in src/router/index.js, we register our components and libraries so that that we can use them in other parts of our apps, like so:

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.min.css'
import WhereContinent from '@/components/WhereContinent'
import WhereCity from '@/components/WhereCity'
import WhereCountry from '@/components/WhereCountry'
import WhereRegion from '@/components/WhereRegion'
import City from '@/components/City'
import Continent from '@/components/Continent'
import Country from '@/components/Country'
import Region from '@/components/Region'
import Win from '@/components/Win'
import Disappoint from '@/components/Disappoint'
Vue.use(Router)
Vue.use(VueMaterial)
Vue.component('where-continent', WhereContinent)
Vue.component('where-city', WhereCity)
Vue.component('where-country', WhereCountry)
Vue.component('where-region', WhereRegion)
Vue.component('city', City)
Vue.component('continent', Continent)
Vue.component('country', Country)
Vue.component('region', Region)
Vue.component('win', Win)
Vue.component('disappoint', Disappoint)
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    }
  ]
})

src.router.index.js

Finally, we get something like this:

This is image title

This is image title

This is image title

This is image title