TL;DR: As Single-Page Applications (SPAs) are fast becoming the de-facto way of developing frontend applications, one of the major issues that developers are facing is how they manage state between all the components spread around their applications. Vuejs apps are not an exception to this. To solve this problem, you will learn, through a practical guide, how to use Vuex to manage the state of your Vue.js apps with ease. Check out this article if you need the reference implementation of the app discussed here.
“Looking forward to learn Vuex to manage the state of your @vuejs apps? This is the place.”
Tweet This
Prerequisites
Before you begin going through this article, there are two technologies that you should already be familiar with: JavaScript, and Vue.js. You might be able to follow along without having ever used Vue.js before. However, if you don't know JavaScript, you will have a hard time (most likely).
Besides this knowledge, you will need Node.js and NPM installed in your machine. Please, check out this resource to install these tools if you don't have them already (by the way, NPM comes bundled into Node.js).
Vuex in Action
In this section, you learn, through a practical guide, how to use Vuex to build a simple Bitcoin dashboard simulation that shows the current price of Bitcoin and updates it as the time passes. Besides the price, your app will also show the percentage increase and the price difference in real-time. Lastly, your app will show the history of the previous four prices for reference.
To make things organized, you will break your app into four components:
- The main component: this one already exists and it is called
.App
- A component that shows the current Bitcoin price and how it differs from the previous one (this one is the green box in the image below).
- A component that shows the percentage change between the current price and the last one, and the time when the update occurred (this one is the blue box in the image below).
- A component that shows the Bitcoin pricing history (this one is the table below the blue and the green boxes).
In the end, you will get an app that looks like this:
Note: You won't use real Bitcoin prices here. You will code some fake sample and then generate random values based on this sample.
Scaffolding a new Vue.js App
To scaffold your new Vue.js app with ease, you will use the Vue CLI tool. As such, if you don't have this CLI already installed in your development environment, open a terminal and run the following command:
npm install -g @vue/cli
After a successful installation, navigate to the directory in which you want to keep your project and run the command below to create a new Vue.js app:
vue create bitcoin-dashboard
Running this command will prompt you to select a preset and, if you have Yarn installed, will ask you if you prefer to use it instead of NPM. Feel free to choose the answers that you prefer, but this article will use
default
for the preset and NPM as the dependency management tool.After making your choices, the Vue CLI tool will scaffold your app and install the basic dependencies. When done, you can move into your new project and run it to see if everything is working properly:
# move into your new app cd bitcoin-dashboard # run it with NPM npm run serve
Now, if you open
in your browser, you will see the following screen:http://localhost:8080/
Then, to shut down your server, you can hit the following keys:
Ctrl
+ C
.Installing Vuex and Other Dependencies
Alongside with the basic dependencies in your Vue.js app, you will also need to install three other dependencies: Bootstrap, Vuex itself, and Font Awesome (the free version).
To install them, make sure you are on the project root and run the command below:
npm install --save \ bootstrap vuex @fortawesome/fontawesome-free
Creating the Vuex Store
To begin setting up your Vuex store, you will create a new directory called
store
inside src
. You will use this directory to hold all files related to your Vuex store.Then, as mentioned before, you will start your Bitcoin app with a fake sample. So, for that, you will need to create a file called
prices.js
inside the store
directory and add the following code to it:const now = Date.now(); const twoSeconds = 2000; const prices = [ { amount: 7322.89, timestamp: now }, { amount: 6322.02, timestamp: now - twoSeconds, }, { amount: 5222.64, timestamp: now - (twoSeconds * 2), }, { amount: 5242.61, timestamp: now - (twoSeconds * 3), } ]; export default prices;
As you can see, in the file that you just created, you are defining and exporting an array of historical Bitcoin prices and the time each one was created. Note that you extract the exact date (
Date.now()
) that your app starts running to define the time that the last price was created. Also, you create a constant called twoSeconds
and use it to calculate the time the other fake prices were created. In other words, you are creating a module that defines an array with four prices:
: the most recent price that gets the date when the app starts running;7322.89
: the second most recent price that gets the date when the app starts running minus two seconds;6322.02
: the third most recent price that gets the date when the app starts running minus four seconds;5222.64
- and
: the oldest price that gets the date when the app starts running minus six seconds.5242.61
With this file in place, the next thing you will need to do is to create a file where you define the Vuex actions available in your application. To do so, create a file called
actions.js
inside the store
directory and add the following code to it:const actions = { UPDATE_PRICE: 'UPDATE_PRICE' }; export default actions;
This file only contains a single action because that is the only thing you will need in your application. Having your actions in a separate file this way helps organize your code better and prevents duplicating string names across your app.
Now, you can finally create your store. For that, create a file called
index.js
inside src
and add the following code into it:import Vue from 'vue'; import Vuex from 'vuex'; import prices from './prices'; // configure Vuex for modules Vue.use(Vuex); const store = new Vuex.Store({ state: { prices: prices }, getters: { currentPrice: state => { return state.prices[0]; }, previousPrice: state => { return state.prices[1]; }, percentageIncrease: (state, getters) => { const currentAmount = getters.currentPrice.amount; const previousAmount = getters.previousPrice.amount; return ( ((currentAmount - previousAmount) / previousAmount) * 100 ).toFixed(2); }, difference: (state, getters) => { const currentAmount = getters.currentPrice.amount; const previousAmount = getters.previousPrice.amount; return (currentAmount - previousAmount).toFixed(2); } }, mutations: { UPDATE_PRICE(state, newPricing) { // remove the oldest price state.prices.pop(); // add the new price state.prices = [newPricing, ...state.prices]; } } }); export default store; export {default as actions} from './actions';
As a lot going on in this file, you will be better off learning it gradually. First, you are importing all modules required:
import Vue from 'vue'; import Vuex from 'vuex'; import prices from './prices';
After that, you are configuring your Vue.js to use Vuex:
Vue.use(Vuex);
Then, you are creating a new instance of a Vuex store (
new Vuex.Store
) with the configuration object where all your settings are declared. In the configuration object passed to Vuex, the first thing you are defining is the default state
object with your fake prices:state: { prices: prices; }
Then, you are creating a
getter
to compute and return the current Bitcoin price (currentPrice
):currentPrice: state => { return state.prices[0]; },
After that, you are creating another
getter
for the previous bitcoin price (previousPrice
):previousPrice: state => { return state.prices[1]; },
The, you are creating the third
getter
for the percentage difference between the current price and the previous one (percentageIncrease
):percentageIncrease: (state, getters) => { const currentAmount = getters.currentPrice.amount; const previousAmount = getters.previousPrice.amount; return ( ((currentAmount - previousAmount) / previousAmount) * 100 ).toFixed(2); },
And, finally, you are creating the last
getter
for the difference between the current and previous price (difference
):difference: (state, getters) => { const currentAmount = getters.currentPrice.amount; const previousAmount = getters.previousPrice.amount; return (currentAmount - previousAmount).toFixed(2); }
Next, you are defining the
mutations
of your Vue.js app. In this application, you only need one mutation which updates the price of Bitcoin:mutations: { UPDATE_PRICE(state, newPricing) { // remove the oldest price state.prices.pop(); // add the new price state.prices = [newPricing, ...state.prices]; } }
As you can see, the
UPDATE_PRICE
mutation receives the new price data in its payload, removes the oldest price from the prices array, and inserts a new one at the beginning of the array.Lastly, you are exporting your
store
and also your actions
so other component components can use it:export default store; export { default as actions } from './actions';
And that's it! Your Vuex store is fully complete.
Defining your Vue.js Components
Now that your store is all set and ready to go, you will start defining your application components. As mentioned before, your app will contain three components (besides the main one that is called
App
). One to show the current Bitcoin price and the difference between the previous and current price. One to show the percentage difference between the current and previous Bitcoin prices and that also display when the last update occurred. One component to show the details of a Bitcoin price.The first one that you will define is the one that will display the current Bitcoin price and the difference between the previous and current price. To define this component, create a file called
CoinPrice.vue
inside the src/components
directory and add the following code to it:<template> <div id="counter" class="bg-success text-white"> <div class="row p-3"> <div class="col-2"> <i class="fas fa-dollar-sign fa-4x"></i> </div> <div class="col-10 text-right"> <h2>{{format(price.amount)}}</h2> ${{difference}} </div> </div> </div> </template> <script> export default { name: 'CoinPrice', computed: { price() { return this.$store.getters.currentPrice; }, difference() { return this.$store.getters.difference; } }, methods: { format: price => { return price.toFixed(2); } } }; </script>
Note: You can also remove the
file that Vue.js created by default for you. You won't use the component defined in this file.HelloWorld.vue
As you can see in the
<script/>
section, the file you just created defines a component called CoinPrice
. This component uses two computed
properties that retrieve state from the Vuex store: price
, which gets the value from $store.getters.currentPrice
; and difference
, which gets the value from $store.getters.difference
. Besides these properties, the file also defines a method called format
to return a cleaner price for display in the component template.Now, in relation to the
<template/>
area, this file is defining a main <div/>
element (with the bg-success
and text-white
Bootstrap classes to make it look better) with a single child that is a Bootstrap row and then it is splitting the content of this element into two sections:
: a section that shows a big (col-2
) Dollar sign icon (fa-4x
);fa-dollar-sign
: a section that will show the current Bitcoin price (col-10
) and theamount
between the current the last price.difference
After defining this component, you will create a similar one that will show the percentage change between the current and the last price and when this change occurred. To define this one, create a file called
PercentChange.vue
inside the src/components
directory and add the following code to it:<template> <div id="counter" class="bg-primary text-white"> <div class="row p-3"> <div class="col-2"><i class="fas fa-percent fa-4x"></i></div> <div class="col-10 text-right"> <h2> {{percentageIncrease}} <small>%</small> </h2> {{formatTimestamp(currentPrice.timestamp)}} </div> </div> </div> </template> <script> export default { name: 'PercentChange', computed: { percentageIncrease() { return this.$store.getters.percentageIncrease; }, currentPrice() { return this.$store.getters.currentPrice; } }, methods: { formatTimestamp: timestamp => { const currentDate = new Date(timestamp); return currentDate.toString().substring(16, 24); } } }; </script>
The component you just defined is quite similar to the previous one. The
<template/>
structure is basically the same: one big icon (a fa-percent
icon in this case) and two values (the percentageIncrease
and the timestamp
now). Also, the <script/>
section is similar, the difference is that now it is showing other relevant data.Now, the last component you will define is the one that will show some historical Bitcoin price. To define this one, create a file called
PriceItem.vue
inside the src/components
directory and add the following code into it:<template> <li class="list-group-item"> <div class="row"> <div class="col-8">${{formatPrice(price.amount)}}</div> <div class="col-4 text-right">{{formatTimestamp(price.timestamp)}}</div> </div> </li> </template> <script> export default { name: 'PriceItem', props: { price: Object }, methods: { formatTimestamp: timestamp => { const date = new Date(timestamp); return date.toString().substring(16, 24); }, formatPrice: amount => { return amount.toFixed(2); } }, }; </script>
This component is even simpler than the other two. All this one is doing is defining a
<li/>
element with the formatted price of some historical value (it can be the current price or an older one) and the time it was created. You will see how to use this component in the next section.Wrapping Up
You are almost done. Now it is just a matter of opening the
App.vue
file and replace its code with this:<template> <div id="app" class="container"> <div class="row"> <div class="col-12"> <h1>Dashboard</h1> </div> </div> <div class="row"> <div class="col-sm-6"> <CoinPrice/> </div> <div class="col-sm-6"> <PercentChange/> </div> </div> <div class="row mt-3"> <div class="col-sm-12"> <div class="card"> <div class="card-header"> Bitcoin Pricing History </div> <ul class="list-group list-group-flush"> <PriceItem v-bind:key="price.timestamp" v-for="price in prices" v-bind:price="price"/> </ul> </div> </div> </div> </div> </template> <script> //Components import CoinPrice from './components/CoinPrice.vue'; import PercentChange from './components/PercentChange.vue'; import PriceItem from './components/PriceItem.vue'; //Store import store, {actions} from './store'; export default { name: 'app', components: { CoinPrice, PercentChange, PriceItem }, store, computed: { prices() { return store.state.prices; } }, created: function () { setInterval(this.triggerNewPrice, 3000); }, methods: { triggerNewPrice: () => { const diff = (Math.random() - Math.random()) * 10; const randomNewPrice = store.getters.currentPrice.amount + diff; store.commit(actions.UPDATE_PRICE, { amount: randomNewPrice, timestamp: Date.now() }); } } }; </script> <style> @import "../node_modules/bootstrap/dist/css/bootstrap.min.css"; @import "../node_modules/@fortawesome/fontawesome-free/css/all.min.css"; #app { font-family: "Avenir", Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; margin-top: 60px; } </style>
The new version of the
App
component, although lengthy, is quite simple. The <template/>
section starts by defining a title (<h1>Dashboard</h1>
). After that, it defines a row where it displays two components: <CoinPrice/>
and <PercentChange/>
. Lastly, it defines a Bootstrap card (<div class="card">
) where it shows the Bitcoin Pricing History. To create this history, the component iterates over each price (v-for="price in prices"
) to create multiple PriceItem
elements.Then, the
<script/>
section starts by importing all the components you defined in the previous section alongside with your Vuex store
and the actions
you created for it. After that, you make your components available to your app by adding them to the components
property of your app. You also make your store
available by adding it to the App
component definition. By importing your store this way, you make it automatically available to all child components of your App
component.After that, three things occur:
- You add a
property calledcomputed
to return all the prices available in your Vuex store.prices
- You add a hook into the
event of the lifecycle of yourcreated
component to trigger a repetitive task that runs every three seconds. This task starts the creation of new random prices:App
.setInterval(this.triggerNewPrice, 3000);
- You define the
inside thetriggerNewPrice
property of yourmethods
component to make the function available to it. This function simply generates a random new price and calls the store'sApp
method with the action to mutate the price (commit
) issuing with it the new random price as a payload.UPDATE_PRICE
Lastly, in the
<style/>
section, you make your app import the css files of Bootstrap and Font Awesome for use in your application. Oh, and you also add a few custom styles for aesthetics.Great! With these changes, you are done with your new Vuex app. Time to test it.
To run the application, issue the following command in a terminal (just make sure you are in your project root):
# from the project root npm run serve
Then, open up your browser and point it to
. There, you should see the app running successfully and the prices updating every three seconds. The percentage difference and price difference should also update:http://localhost:8080/
Cool, isn't it!?
“I just learned how to Vuex to manage the state of @vuejs apps. Really cool!”
Tweet This
Aside: Authenticate a Vue App
Using Auth0, you can protect your applications so that only authenticated users can access them. Let's explore how to authenticate a Vue application.
If you would like to have a more in-depth explanation of protecting a Vue application, you can follow this fantastic article: Beginner Vue.js Tutorial with User Login.
Setting up Auth0
To begin, you will need an Auth0 account. You can sign up for a free Auth0 account here. Once you are logged in, follow these steps to set up an Auth0 application.
- Go to your Auth0 Dashboard and click the "+ CREATE APPLICATION" button.
- Name your new app and select "Single Page Web Applications". Hit "Create".
- In the Settings for your new Auth0 application, add
to the Allowed Callback URLs, Allowed Logout URLs, Allowed Web Origins. Hit "Save Changes" at the bottom of the page.http://localhost:8080
Vue application
You will need to install the Auth0
SDK. To do so, run the following command:auth0-spa-js
npm install @auth0/auth0-spa-js
Next, within your
src/
folder, create an auth
folder. Within the auth
folder, create a file named index.js
. You should now have a path that is src/auth/index.js
.Within that newly created file, paste in the following code:
// src/auth/index.js import Vue from 'vue'; import createAuth0Client from '@auth0/auth0-spa-js'; /** Define a default action to perform after authentication */ const DEFAULT_REDIRECT_CALLBACK = () => window.history.replaceState({}, document.title, window.location.pathname); let instance; /** Returns the current instance of the SDK */ export const getInstance = () => instance; /** Creates an instance of the Auth0 SDK. If one has already been created, it returns that instance */ export const useAuth0 = ({ onRedirectCallback = DEFAULT_REDIRECT_CALLBACK, redirectUri = window.location.origin, ...options }) => { if (instance) return instance; // The 'instance' is simply a Vue object instance = new Vue({ data() { return { loading: true, isAuthenticated: false, user: {}, auth0Client: null, popupOpen: false, error: null, }; }, methods: { /** Authenticates the user using a popup window */ async loginWithPopup(o) { this.popupOpen = true; try { await this.auth0Client.loginWithPopup(o); } catch (e) { // eslint-disable-next-line console.error(e); } finally { this.popupOpen = false; } this.user = await this.auth0Client.getUser(); this.isAuthenticated = true; }, /** Handles the callback when logging in using a redirect */ async handleRedirectCallback() { this.loading = true; try { await this.auth0Client.handleRedirectCallback(); this.user = await this.auth0Client.getUser(); this.isAuthenticated = true; } catch (e) { this.error = e; } finally { this.loading = false; } }, /** Authenticates the user using the redirect method */ loginWithRedirect(o) { return this.auth0Client.loginWithRedirect(o); }, /** Returns all the claims present in the ID token */ getIdTokenClaims(o) { return this.auth0Client.getIdTokenClaims(o); }, /** Returns the access token. If the token is invalid or missing, a new one is retrieved */ getTokenSilently(o) { return this.auth0Client.getTokenSilently(o); }, /** Gets the access token using a popup window */ getTokenWithPopup(o) { return this.auth0Client.getTokenWithPopup(o); }, /** Logs the user out and removes their session on the authorization server */ logout(o) { return this.auth0Client.logout(o); }, }, /** Use this lifecycle method to instantiate the SDK client */ async created() { // Create a new instance of the SDK client using members of the given options object this.auth0Client = await createAuth0Client({ domain: options.domain, client_id: options.clientId, audience: options.audience, redirect_uri: redirectUri, }); try { // If the user is returning to the app after authentication... if ( window.location.search.includes('code=') && window.location.search.includes('state=') ) { // handle the redirect and retrieve tokens const { appState } = await this.auth0Client.handleRedirectCallback(); // Notify subscribers that the redirect callback has happened, passing the appState // (useful for retrieving any pre-authentication state) onRedirectCallback(appState); } } catch (e) { this.error = e; } finally { // Initialize our internal authentication state this.isAuthenticated = await this.auth0Client.isAuthenticated(); this.user = await this.auth0Client.getUser(); this.loading = false; } }, }); return instance; }; // Create a simple Vue plugin to expose the wrapper object throughout the application export const Auth0Plugin = { install(Vue, options) { Vue.prototype.$auth = useAuth0(options); }, };
The comments in this file go over what each section does. To find more details about this file, please visit this blog post section.
Connecting Auth0 and the Vue application
To connect your Auth0 app and your Vue app, you will need to bring over some data from your Auth0 app that you set up earlier. You will want those values protected. To do so, create a file named
auth_config.json
in the root of your Vue application. Then in the .gitignore
, you will want to put that newly created file in there.In that file, put the following values:
// auth_config.json { "domain": "your-domain.auth0.com", "clientId": "yourclientid" }
Back in your Auth0 dashboard, click on the Settings tab of your Auth0 application. You will find the values "Domain" and "Client ID". Copy and paste those values into this file.
Using authentication globally in Vue
To use this authentication globally within the Vue app, you need to update the
src/main.js
file. Delete everything in the file and replace with the following code:// src/main.js import Vue from 'vue'; import App from './App.vue'; import router from './router'; // Import the Auth0 configuration import { domain, clientId } from '../auth_config.json'; // Import the plugin here import { Auth0Plugin } from './auth'; // Install the authentication plugin here Vue.use(Auth0Plugin, { domain, clientId, onRedirectCallback: (appState) => { router.push( appState && appState.targetUrl ? appState.targetUrl : window.location.pathname, ); }, }); Vue.config.productionTip = false; new Vue({ router, render: (h) => h(App), }).$mount('#app');
Log in and log out buttons
In order to use all this, you will want to add "Log In" and "Log Out" buttons. To do that, wherever you would like your buttons to be in your application, add this code within the
<template>
section of that file:<div v-if="!$auth.loading"> <!-- show login when not authenticated --> <a v-if="!$auth.isAuthenticated" @click="login">Log in</a> <!-- show logout when authenticated --> <a v-if="$auth.isAuthenticated" @click="logout">Log out</a> </div>
In that same file within the
<script>
tag, add in these methods:<script> export default { name: 'App', methods: { // Log the user in login() { this.$auth.loginWithRedirect(); }, // Log the user out logout() { this.$auth.logout({ returnTo: window.location.origin, }); }, }, }; </script>
You now have the necessary code to authenticate your Vue.js application!
More resources
Conclusion
In this article, you learned, through a practical guide, how easy it is to use Vuex to manage the state of Vue.js apps.
What is cool about Vuex is that, just like Vue.js, it offers a very clean, easy to use, and declarative API for managing state in your applications. Its simplicity does not take anything away from its effectiveness in managing state, regardless of how complex the application is.
What is your opinion about Vuex? Are you planning on using it in your next production-ready app? Besides that, what is your opinion about the article itself? Let us know!
About the author
Fikayo Adepoju
Full-Stack Developer