Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

A Vue.js App Using Axios With Vuex

TwitterFacebookRedditLinkedInHacker News

In this tutorial we will build a simple Vue.js application which will demonstrate the power of using Vuex as a central data store, where the data will be asynchronously retrieved using Axios for the API requests.

A basic level of HTML, CSS and JavaScript will be beneficial but is not required.

The following topics will be covered:

  • Scaffolding a Vue.js project with Vue CLI and installing required dependancies
  • Setting up the project structure and SCSS
  • Setting up vue-router
  • Setting up the REST API
  • Setting up the Vuex store
  • Conclusion

In this tutorial we are building a dictionary application where a user can enter a word and use a RESTful API to retrieve the word’s meaning and information about the word. The user can then either navigate to a word page where they can access more detailed information about the word or they can navigate to a wordlist where all the queried words are listed.

Our final solution will work as per below:

Vuex Demo for Vue.js

The full source code for the finished application can be found here.

Scaffolding a Vue.js Project with the Vue CLI and Installing Required Dependancies

Let setup a new project using the Vue CLI. The following code will install the Vue CLI system:

$ npm install -g vue-cli
$ vue init webpack vuex-axios-tutorial
$ cd my-project
$ npm install

We are not using the webpack-simple template but rather the more robust webpack template which will allow us some benefits and customizations going forward. It has many features and functionality, be sure to read up more about it to make the most of its capabilities.

New Vue.js Project Settings

The above is the basic setup, you are welcome to change these settings as you please but make sure that you do install vue-router.

Next we need to install the dependancies, vue-router has been installed thanks to Vue CLI, but not Vuex, so lets get started and install all the dependancies that we will need and then start our application:

$ npm install vuex --save
$ npm install axios --save
$ npm install sass-loader node-sass --save-dev
$ npm run dev

That is it for the required plugins and now if you navigate to http://localhost:8080, you will see your freshly installed project. We will get into each plugin and what they do later in the tutorial.

Setting Up the Project Structure

Our dictionary app is going to consist out of four components:

  • The App.vue component, with a header and footer getting information from the other components and wrapping our other components
  • The Home.vue component with an input box to add a word, a link to the queried word view and the word list
  • The WordList.vue component where each word is listed and links to a word view
  • The Word.vue component where detailed information regarding the word is displayed

Lets get started with the App.vue file, update the code as follows:

<template>
    <div id="app">
        <div id="header-nav" class="nav-bars">
            <router-link to="/">Home</router-link> |
            <router-link to="/words">Words</router-link> |
            <router-link :to="latestWord">{{ latestWord }}</router-link>
        </div>
        <router-view/>
        <div id="footer-nav" class="nav-bars">
            My Dictionary 2018 - {{ wordCount }} words has been added.
        </div>
    </div>
</template>

<script>
    export default {
        name: 'App',
        data () {
            return {
                wordCount: 0,
                latestWord: 'Latest_Word'
            }
        }
    }
</script>

<style lang="scss">
    body {
        overflow: hidden;
    }

    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;

        .nav-bars{
            background: cornflowerblue;
            position: fixed;
            width: 100%;
            padding: 10px;
            color: white;

            a {
                color: white;
                text-decoration: none;
            }
        }

        #header-nav {
            top: 0;
            left: 0;
        }

        #footer-nav {
            bottom: 0;
            left: 0;
        }
    }
</style>

In the above we have added the header and footer, the <router-view/> and the SCSS styling for the component. You may have noticed that when setting up the app and installing the dependancies we installed the sass-loader and node-sass packages, which allows us to use SCSS in our components by adding the lang tag to the style tag:

<style lang="scss">

There are hundreds of loaders available which can be added to a project, Vue CLI makes adding these very simple.

We have also added some data to the app:

data () {
    return {
        wordCount: 0,
        latestWord: 'Latest_Word'
    }
}

For now the data is static place holder data, once we have setup our Vuex Store, we will use it to retrieve the data.

Next we will setup the the Home.vue component.

Navigate to the components directory, rename HelloWorld.vue to Home.vue and replace its content with the following:

<template>
    <div class="home">
        <div class="input-container">
            <div class="input-group">
                <input type="text" v-model="word" />
                <button @click="getWord" type="submit">Add Word</button>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'Home',
        data () {
            return {
                word: ''
            }
        },
        methods: {
            getWord (e) {
                alert(this.word)
            }
        }
    }
</script>

<style lang="scss" scoped>
    .home {
        display: flex;
        align-items:center;
        justify-content: center;
        height: calc(100vh);

        .input-container{
            max-width: 500px;
            width:100%;

            .input-group{
                display:flex;
                justify-content: space-between;

                input {
                    box-sizing: border-box;
                    width: calc(100% - 150px);
                    padding: .5rem;
                    border: 2px solid cornflowerblue;
                    font-size: 1rem;
                    border-radius: 0;
                    -webkit-appearance: none;
                }

                button{
                    width:150px;
                    height:50px;
                    -webkit-appearance: none;
                    background: cornflowerblue;
                    color: white;
                    text-transform: uppercase;
                    border: none;
                    font-weight:bold;
                    font-size: 12pt;
                }
            }
        }
    }
</style>

We have created a text input and a button which will retrieve the word data via the RESTful api and add the word to the Vuex Store.

We bind the getWord method to the button click event:

methods: {
    getWord (e) {
        alert(this.word)
    }
}

The getWord method will be used to retrieve the word data and commit it to the Vuex Store. The rest of the code is mostly SCSS. You may have noticed an error or two, this is expected as we have changed the name of the components but we have not updated the changes in the Router, once we update our Router to reflect the changes, these errors will be resolved.

Next we are going to create two new components in our components directory, namely:

  • WordList.vue
  • Word.vue

WordList.vue

<template>
    <div class="wordlist-container">
        <div class="wordlist-columns">
            <h2>My Stored Words:</h2>
            <div class="wordlist">
                <ul>
                    <li v-for="(word, index) in words" :key="index">
                        <router-link v-bind:to="{ name: 'Word', params: { id: word, word: word } }">{{ word }}</router-link>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'WordList',
        data () {
            return {
                words: ['test', 'compare', 'exercise', 'judge', 'matters', 'mouse', 'cycle']
            }
        }
    }
</script>

<style lang="scss" scoped>
    .items {
        .item {
            background: #e0ddd5;
            color: #171e42;
            box-sizing: border-box;
            padding: 10px;
        }
    }

    .wordlist-container{
        margin-top: 60px;

        .wordlist-columns{
            width: 100%;
            display: flex;
            margin: 0 auto;
            text-align: left;
            align-items: center;

            h2{
                flex: 0 0 15%;
                margin-top: 10px;
                text-align: center;
            }

            .wordlist {
                overflow: auto;
                flex: 0 0 85%;

                ul {
                    list-style: none;
                    margin: 0;
                    padding: 0;
                    height: calc(100vh-84px);
                    display: flex;
                    flex-wrap: wrap;
                    margin-left: -10px;
                    margin-top: -10px;

                    li {
                        text-align:  center;
                        background: cadetblue;
                        padding: 10px;
                        margin: 5px;
                        flex: 1 0 auto;
                        box-sizing: border-box;
                        padding: 10px;
                        margin-left: 10px;
                        margin-top: 10px;
                        max-width: 250px;

                        a {
                            color: white;
                            text-decoration: none;
                        }
                    }
                }
            }
        }
    }
</style>

We have setup the WordList.vue component which will list all the words added in the Home.vue component. We have hard coded some words (for testing) and we are using the v-for directive to loop through each one of the words. We are also using the router-link to link to the single word pages. We will go into more detail regarding the Router in the next section.

Word.vue

<template>
    <div class="wordlist-container">
        <div class="wordlist-columns">
            <h2>{{ word }}</h2>
        </div>
        <div class="nav-links">
            <router-link v-bind:to="{ name: 'Home'}">Home</router-link>
            <router-link v-bind:to="{ name: 'WordList'}">Word List</router-link>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'WordList',
        data () {
            return {
                word: this.$route.params.id
            }
        }
    }
</script>

<style lang="scss" scoped>
    .wordlist-container{
        margin-top: 60px;

        .wordlist-columns{
            width: 100%;
            display: flex;
            margin: 0 auto;
            text-align: left;
            align-items: center;

            h2{
                flex: 0 0 15%;
                margin-top: 10px;
                text-align: center;
            }

            .wordlist {
                overflow: auto;
                flex: 0 0 85%;

                ul {
                    list-style: none;
                    margin: 0;
                    padding: 0;
                    height: calc(100vh-84px);
                    display: flex;
                    flex-wrap: wrap;
                    margin-left: -10px;
                    margin-top: -10px;

                    li {
                        text-align:  center;
                        background: cadetblue;
                        padding: 10px;
                        margin: 5px;
                        flex: 1 0 auto;
                        box-sizing: border-box;
                        padding: 10px;
                        margin-left: 10px;
                        margin-top: 10px;

                        a {
                            color: white;
                            text-decoration: none;
                        }
                    }
                }
            }
        }
        .nav-links{
            display: flex;
            width: 100%;
            position: fixed;
            bottom: 62px;
            left: 0;
            justify-content: space-between;

            a {
                padding: 20px 50px;
                text-align: center;
                background-color: royalblue;
                -webkit-box-flex: 0;
                -ms-flex: 0 0 48%;
                flex: 0 0 48%;
                margin: 0 20px;
                box-sizing: border-box;
                display: block;
                text-decoration: none;
                color: white;
            }
        }
    }
</style>

This template will display all our word information and we will populate this with some dynamic data. One important thing to note is that we are retrieving the word via the URL parameter passed with our Vue Router link in the WordList.vue component.

<router-link v-bind:to="{ name: 'Word', params: { id: word, word: word } }">{{ word }}</router-link>

Here we are passing two URL parameters, namely the id and the word. The id would normally be used for the URL but for this example, I am using the word for the URL and later we will also use the word in our Rest API call.

Now that our templates has been setup and most of the SCSS has been applied, we can start with the real fun stuff!

Setting Up the Vue Router

From the Vue Router webpage:

Creating a Single-page Application with Vue + Vue Router is dead simple. With Vue.js, we are already composing our application with components. When adding vue-router to the mix, all we need to do is map our components to the routes and let vue-router know where to render them.

I would highly recommend spending sometime familiarizing yourself with the Vue Router.

When we setup up our project using the Vue CLI, it has already installed and setup the Vue Router. Our main.js file includes: import router from './router' and the router is also added in our app initialization:

new Vue({
    el: '#app',
    router,
    components: { App },
    template: '<App/>'
})

Then in our App.vue component, we see the <router-view />, which is where we display our different views (components).

We will need to update our index.js file, inside the router directory as follows:

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
import WordList from '@/components/WordList'
import Word from '@/components/Word'

Vue.use(Router)

export default new Router({
    routes: [
        {
            path: '/',
            name: 'Home',
            component: Home
        },
        {
            path: '/words',
            name: 'WordList',
            component: WordList
        },
        {
            path: '/words/word/:id',
            name: 'Word',
            component: Word
        }
    ]
})

Here we are connecting all our components to the paths in our application. The Home component is loaded when access the ‘/’ URL, the WordList component when we access the ‘/words’ URL and lastly the Word component is loaded when we access the ‘/words/word/:id’, this may be a good time to ensure that everything is up and running and working like it should.

Setting up the REST API

For our project to retrieve word meanings and word information, we are going to need to have to connect to an API. After experimenting with several dictionary API’s I decided to go with the Pearson Dictionary API.  We can use it without an account and it is super simple to setup and get going.

Feel free to visit the Pearson Website and learn more about the API but this is not required as we will cover all the necessary information below.

In the root directory, create a services folder with two files:

  • Api.js
  • WordService.js

Api.js

import axios from 'axios'

export default() => {
    return axios.create({
        baseURL: `http://api.pearson.com/v2/dictionaries`,
        withCredentials: false,
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
    })
}

This is our base API settings, we specify the URL that we are connecting to, that we do not need credentials and we also specify the headers.

Accept

Tells the server what MIME-type of resource the browser is looking for.

Content-Type

Tells the server what the data type that the application is sending is. Which is not required for our App as we are not using any PUT or POST requests but good to know nonetheless.

WordService.js

import Api from '@/services/Api'

export default {
    getWord (params) {
        return Api().get('/wordwise/entries?limit=1&headword=' + params.word)
    }
}

The above code will use our Api.js file to perform a GET request and then get the word which we add on our home page. We are also limiting the results to one as we just want one definition of the word.

For now we are done with our API, we will come back to add some more in depth queries.

Head back to our Home.Vue component and replace the current input box code with the following:

<input type="text" v-on:keydown.enter="getWord" v-model="word" />

We are binding the enter key press with our getWord method.

Next lets reference our API service and complete our getWord method:

import WordService from '@/services/WordService'
export default {
    name: 'Home',
    data () {
        return {
            word: '',
            wordData: ''
        }
    },
    computed: {
        wordMeaning () {
            if (this.wordData) {
                return this.wordData.senses[0].definition
            }
            return ''
        }
    },
    methods: {
        async getWord () {
            const response = await WordService.getWord({ word: this.word })
            this.wordData = response.data.results[0]
        }
    }
}

With the above code, we can do an API call and bind the returned data to our Home component. To see our result lets add the following to our component HTML below the .input-group div:

<template>
    ...
    <div class="word-meaning">
        {{ wordMeaning || error }}
    </div>
    ...
</template>

Now when you enter a word and hit the enter key or click on the “Add Word” button, you should see the definition below the input box.

Lets finish it up with some validations and styling, copy the below code to the Home.vue component:

<template>
    <div class="home">
        <div class="input-container">
            <div class="input-group">
                <input type="text" v-on:keydown.enter="getWord" v-model="word" />
                <button @click="getWord" type="submit">
                    Add Word
                </button>
            </div>
            <div class="word-meaning">
                {{ wordMeaning || error }}
                <div class="word-links" v-if="wordData">
                    <router-link v-bind:to="{ name: 'Word', params: { id: word, word: word, data:wordData } }">Read More</router-link>
                    <router-link v-bind:to="{ name: 'WordList'}">Word List</router-link>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    import WordService from '@/services/WordService'
    export default {
        name: 'Home',
        data () {
            return {
                word: '',
                wordData: '',
                error: ''
            }
        },
        computed: {
            wordMeaning () {
                if (this.wordData) {
                    return this.wordData.senses[0].definition
                }
                return ''
            }
        },
        methods: {
            async getWord () {
                if (this.word === '') {
                    this.error = 'Please enter a word.'
                    this.wordData = ''
                    return false
                }
                const response = await WordService.getWord({ word: this.word })

                let responses = response.data.results

                if (responses.length === 0) {
                    this.error = 'Your word could not be found and was not added.'
                    this.wordData = ''
                    return false
                }

                this.wordData = responses[0]
            }
        }
    }
</script>

<style lang="scss" scoped>
    .home{
        display: flex;
        align-items:center;
        justify-content: center;
        height: calc(100vh);

        .input-container{
            max-width: 500px;
            width:100%;

            .input-group{
                display:flex;
                justify-content: space-between;

                input {
                    box-sizing: border-box;
                    width: calc(100% - 150px);
                    padding: .5rem;
                    border: 2px solid royalblue;
                    font-size: 1rem;
                    border-radius: 0;
                    -webkit-appearance: none;
                }

                button{
                    width:150px;
                    height:50px;
                    -webkit-appearance: none;
                    background: royalblue;
                    color: white;
                    text-transform: uppercase;
                    border: none;
                    font-weight:bold;
                    font-size: 12pt;
                }
            }
        }

        .word-meaning {
            margin: 20px 0;
            font-size: 18px;
            font-weight: bold;

            .word-links{
                margin: 30px 0 20px 0;
                font-size: 14px;
                font-weight: regular;

                a {
                    border: 1px solid royalblue;
                    text-decoration: none;
                    padding: 5px 12px 3px 12px;
                    margin: 0 10px;
                    text-transform: uppercase;

                    &:focus {
                        color: royalblue;
                    }
                }
            }
        }
    }
</style>

We have added some styling, HTML and also some validations to ensure that the user enters a word and that the word is valid. We also added two button, that allow a user to view the word page or the word list once the word information has been retrieved.

If you have done a sanity check, you may have noticed that currently the Word page is just displaying a blank page, what we want is for the Word page to retrieve all information related to the word.

We will need to add another API call to our WordService in the services folder:

import Api from '@/services/Api'

export default {
    getWord (params) {
        return Api().get('/wordwise/entries?limit=1&headword=' + params.word)
    },
    getWordDetails (params) {
        return Api().get('/entries/' + params)
    }
}

The new API call, returns more in-depth information about the word. We will call this once a user clicks on a Word.

Next we will update our Word.vue component as follows:

<template>
    <div class="wordlist-container">
        <div class="wordlist-columns">
            <h2>{{ word }} <span v-if="partOfSpeech">[{{ partOfSpeech }}]</span>:</h2>
            <div class="word-info">
                <ul class="words" v-if="senses">
                    <li v-for="(sense, index) in senses" :key="index">
                        <div class="word-definition">
                            <h3>Definition #{{ index + 1}}</h3>
                            <blockquote><p>{{ sense.definition }}</p></blockquote>
                            <div class="example" v-if="sense.examples">
                                <h4>Examples: </h4>
                                <ul class="examples">
                                    <li v-for="(example, indexEx) in sense.examples" :key="indexEx">
                                        {{ example.text }}
                                    </li>
                                </ul>
                            </div>
                        </div>
                    </li>
                </ul>
            </div>
        </div>
        <div class="nav-links">
            <router-link v-bind:to="{ name: 'Home'}">Home</router-link>
            <router-link v-bind:to="{ name: 'WordList'}">Word List</router-link>
        </div>
    </div>
</template>

<script>
    import WordService from '@/services/WordService'
    export default {
        name: 'WordList',
        data () {
            return {
                word: this.$route.params.id,
                wordId: this.$route.params.data.id,
                partOfSpeech: '',
                senses: ''
            }
        },
        mounted () {
            this.getWordDetails(this.wordId)
        },
        methods: {
            async getWordDetails (params) {
                const response = await WordService.getWordDetails(params)
                let data = response.data.result
                this.partOfSpeech = data.part_of_speech
                this.senses = data.senses
            }
        }
    }
</script>

<style lang="scss" scoped>
    .wordlist-container {
        margin-top: 60px;

        .wordlist-columns {
            width: 100%;
            display: flex;
            margin: 0 auto;
            text-align: left;
            align-items: center;

            ul {
                list-style: none;
                padding: 0;
                margin: 0;
            }

            h2 {
                flex: 0 0 25%;
                margin-top: 10px;
                text-align: center;
                align-self: flex-start;
            }

            .word-info {
                overflow: auto;
                flex: 0 0 75%;

                .words {
                    height: calc(100vh - 200px);
                    overflow: auto;
                }

                h4{
                    margin-bottom: 10px;
                }
            }

            .examples {
                list-style: circle;
                margin: 0 0 30px 30px;

                li {
                    line-height: 30px;
                }
            }
        }

        .nav-links {
            display: flex;
            width: 100%;
            position: fixed;
            bottom: 62px;
            left: 0;
            justify-content: center;

            a {
                padding: 20px 50px;
                text-align: center;
                background-color: royalblue;
                -webkit-box-flex: 0;
                flex: 0 0 47%;
                margin: 0 20px;
                box-sizing: border-box;
                display: block;
                text-decoration: none;
                color: white;
            }
        }
        blockquote {
            background: #f9f9f9;
            border-left: 10px solid #ccc;
            margin: 1.5em 10px;
            padding: 0.5em 10px;
            quotes: "\201C""\201D""\2018""\2019";
            width: fit-content;
        }

        blockquote:before {
            color: #ccc;
            content: open-quote;
            font-size: 4em;
            line-height: 0.1em;
            margin-right: 0.25em;
            vertical-align: -0.4em;
        }
        blockquote p {
            display: inline;
        }
    }
</style>

We now display all the data which we retrieve for our word. We display:

  • The different word definitions
  • Whether the word is a noun, verb or adjective
  • Examples of how the word is used

There is a lot more information available from the API and you can adjust this to load the data as you please.

No new concepts are being applied here, we are just once again calling the API but this time we do it once the component has mounted:

mounted () {
    this.getWordDetails(this.wordId)
},

Then we call the this.getWordDetails method:

methods: {
    async getWordDetails (params) {
        const response = await WordService.getWordDetails(params)
        let data = response.data.result
        this.partOfSpeech = data.part_of_speech
        this.senses = data.senses
    }
}

The getWordDetails method adds the additional information to our template. Most of the logic which hides and displays elements based on whether the information is available occurs in the HTML using the v-if directive.

You may have noticed that we have not touched our WordList.vue or our App.vue, in the next section, where we wire up our VUEX store, we will connect everything together.

Setting up the VUEX store

From the Vuex site:

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. It also integrates with Vue’s official devtools extension to provide advanced features such as zero-config time-travel debugging and state snapshot export / import.

Lets get our store up and running. Firstly we need to create a new folder called “store” within our “src” directory. Once we have created this folder, we are going to create an index.js file inside the store directory.

For now lets add the following code to the index.js file:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store(
    {
        state: {
            words: []
        },
        mutations: {
            addWord (state, word) {
                state.words.push(word)
            },
            deleteWord (state, word) {
                state.words = state.words.filter(obj => obj.headword !== word)
            }
        }
    }
)

We have now setup our store and our initial state, due to the simple nature of our application, we will only have words in our state.

Lets update our Main.js as follows:

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
    el: '#app',
    store,
    router,
    components: { App },
    template: '<App/>'
})

In the above code we are just importing and including our Store into our app just as we have done with our Router.

As with all new technologies the Vuex store may seem a little daunting and hard to wrap your head around at first but once you have snapped the basic principles, you won’t believe that you ever did a project without it.

It is recommended that you see read through the Vuex Store documentation, it is concise and won’t take longer than an hour or two.

Now that we created the Store and done the initial setup, lets tie everything together.

Firstly we want to edit our Home.vue component with the following code:

<template>
    <div class="home">
        <div class="input-container">
            <div class="input-group">
                <input type="text" v-on:keydown.enter="getWord" v-model="word" />
                <button @click="getWord" type="submit">
                    Add Word
                </button>
            </div>
            <div class="word-meaning">
                {{ wordMeaning || error }}
                <div class="word-links" v-if="wordData">
                    <router-link v-bind:to="{ name: 'Word', params: { id: word, word: word, data:wordData } }">Read More</router-link>
                    <router-link v-bind:to="{ name: 'WordList'}">Word List</router-link>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    import WordService from '@/services/WordService'
    export default {
        name: 'Home',
        data () {
            return {
                word: '',
                wordData: '',
                error: ''
            }
        },
        computed: {
            wordMeaning () {
                if (this.wordData) {
                    return this.wordData.senses[0].definition
                }
                return ''
            },
            words () {
                return this.$store.state.words
            }
        },
        methods: {
            async getWord () {
                if (this.word === '') {
                    this.error = 'Please enter a word.'
                    this.wordData = ''
                    return false
                }
                const response = await WordService.getWord({ word: this.word })

                let responses = response.data.results

                if (responses.length === 0) {
                    this.error = 'Your word could not be found and was not added.'
                    this.wordData = ''
                    return false
                }

                this.wordData = responses[0]
                let words = this.words
                if (!words.filter(word => word.headword === this.word).length > 0) {
                    this.$store.commit('addWord', this.wordData)
                }
            }
        }
    }
</script>

<style lang="scss" scoped>
    .home{
        display: flex;
        align-items:center;
        justify-content: center;
        height: calc(100vh);

        .input-container{
            max-width: 500px;
            width:100%;

            .input-group{
                display:flex;
                justify-content: space-between;

                input {
                    box-sizing: border-box;
                    width: calc(100% - 150px);
                    padding: .5rem;
                    border: 2px solid royalblue;
                    font-size: 1rem;
                    border-radius: 0;
                    -webkit-appearance: none;
                }

                button{
                    width:150px;
                    height:50px;
                    -webkit-appearance: none;
                    background: royalblue;
                    color: white;
                    text-transform: uppercase;
                    border: none;
                    font-weight:bold;
                    font-size: 12pt;
                }
            }
        }

        .word-meaning {
            margin: 20px 0;
            font-size: 18px;
            font-weight: bold;

            .word-links{
                margin: 30px 0 20px 0;
                font-size: 14px;
                font-weight: regular;

                a {
                    border: 1px solid royalblue;
                    text-decoration: none;
                    padding: 5px 12px 3px 12px;
                    margin: 0 10px;
                    text-transform: uppercase;

                    &:focus {
                        color: royalblue;
                    }
                }
            }
        }
    }
</style>

Now we are writing to our store and also reading from our store. We committing the word to the store using the addWord mutation, this will only add a word if the word is not already in the array. Then we are reading from our store using the computed word method.

Lets update our WordList.vue with the following code:

<template>
    <div class="wordlist-container">
        <div class="wordlist-columns">
            <h2>My Stored Words:</h2>
            <div class="wordlist">
                <ul>
                    <li v-for="(word, index) in words" :key="index">
                        <router-link v-bind:to="{ name: 'Word', params: { id: word.headword , word: word.headword, data:word} }">{{ word.headword }}</router-link>
                    </li>
                </ul>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'WordList',
        computed: {
            words () {
                return this.$store.state.words.slice().reverse()
            }
        }
    }
</script>

<style lang="scss" scoped>
    .wordlist-container{
        margin-top: 60px;

        .wordlist-columns{
            width: 100%;
            display: flex;
            margin: 0 auto;
            text-align: left;
            align-items: center;

            h2{
                flex: 0 0 25%;
                margin-top: 10px;
                text-align: center;
            }

            .wordlist {
                overflow: auto;
                flex: 0 0 75%;

                ul {
                    list-style: none;
                    margin: 0;
                    padding: 0;
                    height: calc(100vh-84px);
                    display: flex;
                    flex-wrap: wrap;
                    margin-left: -10px;
                    margin-top: -10px;

                    li {
                        text-align:  center;
                        background: cadetblue;
                        padding: 10px;
                        margin: 5px;
                        flex: 1 0 auto;
                        box-sizing: border-box;
                        padding: 10px;
                        margin-left: 10px;
                        margin-top: 10px;
                        width: 200px;

                        a {
                            color: white;
                            text-decoration: none;
                        }
                    }
                }
            }
        }
    }
</style>

This may be the most simplest of the components as it just reads from the store and reverses the array to show the latest words first.

Now time for our Word.vue component:

<template>
    <div class="wordlist-container">
        <div class="wordlist-columns">
            <h2>{{ word }} <span v-if="partOfSpeech">[{{ partOfSpeech }}]</span>:</h2>
            <div class="word-info">
                <ul class="words" v-if="senses">
                    <li v-for="(sense, index) in senses" :key="index">
                        <div class="word-definition">
                            <h3>Definition #{{ index + 1}}</h3>
                            <blockquote><p>{{ sense.definition }}</p></blockquote>
                            <div class="example" v-if="sense.examples">
                                <h4>Examples: </h4>
                                <ul class="examples">
                                    <li v-for="(example, indexEx) in sense.examples" :key="indexEx">
                                        {{ example.text }}
                                    </li>
                                </ul>
                            </div>
                        </div>
                    </li>
                </ul>
            </div>
        </div>
        <div class="nav-links">
            <router-link v-bind:to="{ name: 'Home'}">Home</router-link>
            <router-link v-bind:to="{ name: 'WordList'}">Word List</router-link>
            <a href="#" @click="deleteWord(word)">Delete Word</a>
        </div>
    </div>
</template>

<script>
    import WordService from '@/services/WordService'
    export default {
        name: 'WordList',
        data () {
            return {
                word: this.$route.params.word,
                wordId: this.$route.params.data.id,
                senses: '',
                partOfSpeech: ''
            }
        },
        mounted () {
            this.getWordDetails(this.wordId)
        },
        computed: {
            words () {
                return this.$store.state.words
            }
        },
        methods: {
            async getWordDetails (params) {
                const response = await WordService.getWordDetails(params)
                let data = response.data.result
                this.partOfSpeech = data.part_of_speech
                this.senses = data.senses
            },
            deleteWord (word) {
                this.$store.commit('deleteWord', word)
                this.$router.push({ name: 'Home' })
            }
        }
    }
</script>

<style lang="scss" scoped>
    .wordlist-container {
        margin-top: 60px;

        .wordlist-columns {
            width: 100%;
            display: flex;
            margin: 0 auto;
            text-align: left;
            align-items: center;

            ul {
                list-style: none;
                padding: 0;
                margin: 0;
            }

            h2 {
                flex: 0 0 25%;
                margin-top: 10px;
                text-align: center;
                align-self: flex-start;
            }

            .word-info {
                overflow: auto;
                flex: 0 0 75%;

                .words {
                    height: calc(100vh - 200px);
                    overflow: auto;
                }

                h4{
                    margin-bottom: 10px;
                }
            }

            .examples {
                list-style: circle;
                margin: 0 0 30px 30px;

                li {
                    line-height: 30px;
                }
            }
        }

        .nav-links {
            display: flex;
            width: 100%;
            position: fixed;
            bottom: 62px;
            left: 0;
            justify-content: center;

            a {
                padding: 20px 50px;
                text-align: center;
                background-color: royalblue;
                -webkit-box-flex: 0;
                flex: 0 0 31%;
                margin: 0 20px;
                box-sizing: border-box;
                display: block;
                text-decoration: none;
                color: white;
            }
        }
        blockquote {
            background: #f9f9f9;
            border-left: 10px solid #ccc;
            margin: 1.5em 10px;
            padding: 0.5em 10px;
            quotes: "\201C""\201D""\2018""\2019";
            width: fit-content;
        }

        blockquote:before {
            color: #ccc;
            content:'\201C';
            font-size: 4em;
            line-height: 0.1em;
            margin-right: 0.25em;
            vertical-align: -0.4em;
        }
        blockquote p {
            display: inline;
        }
    }
</style>

At this stage we have a fully functional application which demonstrates how information can be shared among several Vue.js components.

The last piece of the puzzle is the Word.vue file, where we reference the store to delete a word. Here you can really see the magic of the Vuex Store, once a word has been deleted a user is redirected back to the home page. You will notice that thee word count has decreased and if the word happened to be the be the latest added word, this will also be updated the latest word in the array will be displayed.

Conclusion

From this basic application the power and need for a store is very evident. As soon as you start to think of state management, then you should be implementing state management. I recommend doing it earlier than later.

This application can be improved, off the bat:

  • Unit tests can be implemented
  • The Word.vue component can be refactored to using more of the data from the store that is already available
  • It can be connected to backend persisting data instead of losing it each section (page refresh)

I encourage you to pick this application apart and feel free to ask any questions below, I will gladly get back to you.

Siegfried Grimbeek

Siegfried Grimbeek

Passionate about the internet and technology as a whole, Siegfried Grimbeek has been actively developing websites for almost ten years. Currently he is focusing on JavaScript specialising in frameworks such as React and Vue.js and is always looking for ways to improve his development knowledege.