Vue.js Tutorial - Basic Inventory

Vue.js Tutorial - Basic Inventory

Basics of Vue 3, Axios & Pinia

Introduction

This course outline has been prepared to give you a deeper understanding of the abilities and features of this lightweight JS library for small and large-scale applications.

Starting with the 1st part of this course, the points touched here include setting up Pinia, connecting Pinia stores with components, setting up Vue Router, basic/advanced routing, template conversion, etc.

By the end of this course, you will be able to build various degrees of applications and expand your horizon in the front-end field to unimaginable lengths - all on your own… Confidently!

Prerequisites

Backend Service Setup

The backend service that will power this application can be accessed via any of the two (2) approaches listed below:

  1. vue-training-backend.danikoko.com

Local machine setup

Connect to the repository of the backend service here and get it cloned and set up on your local machine (the database is found in the root folder of the codebase).

Steps breakdown:

  • Launch the WAMP server.

  • Open phpMyAdmin and create a database named vue_training.

  • Go to your www folder found in the wamp64 folder on your Local Disk drive in your machine.

  • Open a terminal in this folder.

  • Run this command below:

  • git clone https://github.com/Danikoko/vue-backend-service.git

  • Once the download is complete run the command below:

  • cd vue-backend-service

  • Now run this command: composer install

  • If you do not have composer installed, carefully follow the instructions to install it and return to the step above.

  • Once that is complete, run this command: php artisan migrate

  • On completion of the above command, run this: php artisan db:seed --class=CreateInventoriesSeeder

  • If the command above fails, open the vue-backend-service folder just created inside the wamp64 folder and locate a vuetraining.sql file. Open up phpMyAdmin and create a database named vue_training. Once that is done, import the vuetraining.sql file in the database and the process is done.

  • Now run the following command: php artisan jwt:secret

  • Finally, run this command: php artisan serve

  • Great work! You have your backend server running successfully.

Vue Application Setup

For our application, we will make use of Vite for scaffolding. Let us have a walkthrough on the steps to take to get our application up and running.

Steps breakdown:

  • Head over to the directory where you want your Vue project installed and open a terminal (command prompt or Windows PowerShell preferred) there.

  • Run this command: npm create vite@latest

  • Follow the prompts that display: Ensure Vue is selected as the preferred framework and JavaScript is the preferred variant. Doing this runs an installation of the Vue.js project in a jiffy.

  • Follow the instructions displayed below:

  • If you follow the instructions correctly, you should have your Vue.js project running and the project running on a generated server port similar to mine displayed below:

  • Visit the link displayed (http://localhost:5173 for me; check for yours 😉) on your browser and you have Vue.js running on your system!

  • Now I would like to introduce you to a very helpful extension for building Vue components. This extension is called Volar. So head over to your extensions marketplace on your code editor, search for the Volar extension, and install it on your editor.

  • With this extension, you can quickly set up Vcaffolding on your Vue component files by simply typing “vue”. This brings up a highlighted list of options provided by the Volar extension which you can pick:

  • Selecting the 1st option generates a piece of code that serves as the base for every Vue component to be created. Your file should look like this:

      <template>
          <!-- Component markup -->
      </template>
    
      <script>
      export default {
          // Component functionality
      }
      </script>
    
      <style>
          /* Component styling */
      </style>
    
  • Now you’re set. Let’s proceed!

Vue Router Configuration

This section introduces us to routing and navigating pages in our Single Page Application with the Vue.js router – Vue Router. This tool helps ensure that routing for Vue.js is expressive, configurable and convenient. It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze.

Before installing the package in our project, let us adjust our project structure (hopefully your code editor is still open – if not kindly open it). Inside the src folder, create a new folder and have it named pages.

Within the newly created pages folder, create two Vue files named Home.vue and Login.vue. Your folder structure should look something like the image displayed below:

Open the Home.vue file, run the Volar scaffold with and add an <h1> element in the <template> element with the text: WELCOME TO THE HOME PAGE. You can use whatever text you like – just try and make it clear that you’re on the home page. You should have something like this:

<template>
    <h1>WELCOME TO THE HOME PAGE</h1>
</template>

<script>
export default {
    // Component functionality
}
</script>

<style>
    /* Component styling */
</style>

Do the same thing above to the Login.vue file but replace the text in the <h1> element with something that describes a login page. Done? Great!

You might have guessed it – these files will serve as two different pages on our application.

Now that we have that done, let’s install Vue Router and see how it works. Open up your terminal and run the following command npm install vue-router@4.This will install the package – however, we’ll have to set it up properly to be able to see it in action.

Let’s start by creating a router folder inside the src folder of our code base. Inside this folder, create an index.js file. This file will house our route configuration – copy the code shown below and save the file.

import { createRouter, createWebHistory } from 'vue-router';
import Home from '../pages/Home.vue';
import Login from '../pages/Login.vue';

const routes = [
    {    
        path: '/',
        component: Home
    },
    {
        path: '/login',
        component: Login
    }
];

const router = createRouter({
    scrollBehavior: function() {
        return { x: 0, y: 0 };
    },
    history: createWebHistory(),
    routes
});

export default router;

The code above illustrates what every router configuration should look like. Set up your routes like the one shown above and you can build the most sophisticated of front-end applications. We’re not done just yet.

Head over to the main.js file in the src folder and update the content to look like the code shown in the code below:

import { createApp } from 'vue';
import router from './router';
import './style.css';
import App from './App.vue';

createApp(App).use(router).mount('#app');

Finally, update the App.vue file in the src folder to contain the code below:

<template>
    <router-view />
</template>

<script setup>
</script>

<style scoped>
</style>

With this, head over to your browser and attempt the following URLs to see the two (2) new pages we’ve created (assuming your application is still running in development mode):

  1. http://localhost:5173

  2. http://localhost:5173/login

Our routes are up and running now. Feel free to play around and create new pages and routes to get the full feel of this process.

Pinia Configuration

In the previous chapter, we set up a crucial part of building standard front-end applications in the installation and configuration of Vue Router.

This chapter dives into setting up Pinia [stores] for front-end applications.

The official documentation defines Pinia as a store library for Vue, it allows you to share a state across components/pages

In my terms, I would refer to Pinia as a data center for serving data to all our components, everywhere. Think of a restaurant with 100 customers, each with their separate orders. A waiter takes each order and heads to the control room where each order is processed and served to the customer. The customers here are the numerous components our app may have while the control room is the Pinia store that serves different information to these components.

I think we have a basic understanding of what it does now (well I hope lol). We’ll learn more as we develop with it. Let’s have it installed.

Open up your terminal and run the following command npm install pinia.

This will install the package. Now to set it up to function properly on our application.

Update your main.js file to include and use Pinia:

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import router from './router';
import './style.css';
import App from './App.vue';

const pinia = createPinia();

createApp (App)
.use(pinia)
.use(router)
.mount('#app');

The next thing to do is to create a store folder in your src folder. Inside the store folder, create an inventory.js file and input the code below into this file:

import { defineStore } from 'pinia';

const useInventoryStore = defineStore('inventory', {
    state: () => ({
        totallLaptops: 0,
        pricePerLaptop: 250000
    }),
    getters: {
        totallLaptopPrices: (state) => {
            return state.totallaptops * state.pricePerLaptop
        }
    },
    actions: {
        addLaptop() {
            this.totallLaptops++;
        }
    }
});

export default useInventoryStore;

We have our store running now! Let’s see it in action on our Home page. Open the Home.vue page and replace the content there with the content below:

<template>
    <div>
        <h1>WELCOME TO THE HOME PAGE</h1>
        <h2>Price Per Laptop: {{ pricePerLaptop }}</h2>
        <h2>Total Laptops: {{ totallLaptops }}</h2>
        <h2>Total Laptop Prices: {{ totallLaptopPrices }}</h2>
        <button @click="addLaptop()">Add Laptop</button>
    </div>
</template>

<script>
    import useInventoryStore from '../store/inventory';
    import { mapState, mapActions } from 'pinia';
    export default {
        computed: {
            ...mapState(useInventoryStore, [
                'totalLaptops',
                'pricePerLaptop',
                'totalLaptopPrices'
            ])
        },
        methods: {
            ...mapActions(useInventoryStore, [
                'addLaptop'
            ])
        }
    }
</script>

<style></style>

Save this and head over to the home page on your browser (http://localhost:5173) and you’ll see Pinia in action.

Play around with what you see on your browser and observe all the things we’ve done in this chapter. I promise you you’ll begin to get the whole idea and concept of state management with Pinia.

Now that we have a functional inventory.js store, let us create a user.js store. This store will contain authenticated users’ details which may be shared across the multiple components of the application. These details will include user bio, auth status, token, etc.

Head over to the Pinia store folder where we just created an inventory.js store and create a store named user.js. Once done with that, paste in the code below and save:

import { defineStore } from 'pinia';

const useUserStore = defineStore('user', {
    state: () => ({
        //
    }),
    getters: {
        userIsAuth: () => false
    },
    actions: {
        //
    }
});

export default useUserStore;

Vue.js Devtools: Do you know you can inspect your Pinia data and everything that has to do with your application all in your browser? Ladies and gentlemen, drum rolls please, I introduce to you:

Vue.js devtools provides tools and features for debugging and testing your Vue applications in development mode. You can inspect data, routes, stores, errors, methods, etc all with this robust tool.

Search your browser’s (preferably Google Chrome) extensions library/marketplace and get Vue.js devtools installed to use it.

Once installed, you can bring it up by inspecting your vue.js page on the browser either by pressing the keyboard shortcut [Ctrl+Shift+I] for Windows and [Cmd+Shift+I] for macOS or right-click anywhere on the page and left-clicking Inspect on the popup that comes up.

You should see something like this afterward:

Select Vue from the horizontal list and you’ll be brought to the Vue.js devtools environment.

Take a tour of this tool and see what interesting things you can find. I bet you will.

Template Conversion

We’ve achieved a lot so far. I believe you now know how to set up Vue applications, and install and set up Vue Router and Pinia. Fantastic. We can proceed to build our application.

We will be using a template that has already been converted for you. Simply use this link to clone the branch/repository to your system:

github.com/Danikoko/vue-training-frontend/t..

Don’t forget to run npm install to get the necessary packages installed.

Your codebase should look like this:

You may see some new files. They’re self-explanatory. There hasn’t been any major change to our app. Just a few additional files which are similar in functionality to the ones we had before.

In the next chapter, we’re going to start calling APIs and consuming endpoints to save in our Pinia stores and serve our application as a whole.

See you there!

Consuming Endpoints: User Registration

This is where things get interesting. We are going to make API requests to our server side and use the data fetched to render our Vue.js application - starting from user registration.

The first thing we would do is add a final Pinia store named general.js. The “general” store will house constant values which will not be [often] changed while developing this application. Examples of such values may be API URLs, meta values, developer information, etc.

Following the store creation, your general.js store and codebase should look like this:

To make API calls to our server, we will use Axios.js - a javascript tool specifically for this purpose.

Open up your terminal and run this command:

npm install axios

Once installation is complete, head over to the Register.vue page and write some code that handles user creation.

On the Register.vue page, update your <script> element at the bottom of the page to the following piece of code:

<script>
    import axios from 'axios';
    import useGeneralStore from '../store/general';
    import { mapActions, mapState } from 'pinia';
    export default {
        data: () = {
            return {
                name: '', 
                email: '',
                password: '',
                submitting: false
            }
        },
        computed: {
            ...mapState(useGeneralStore, [
                'API_URL',
            ])
        },
        methods: {
            registerUser() {
                const _this = this;
                _this.submitting = true;
                axios.post(`${_this.API_URL}register`, {
                    name: _this.name,
                    email: _this.email,
                    password: _this. password
                }).then(RESPONSE => {
                    alert(RESPONSE.data.message);
                }).catch(ERROR => {
                    alert(ERROR.response.data.message);
                }).then(() => {
                    _this.submitting = false;
                });
            }
        }
    }
</script>

Here we import axios, the general.js store, and the mapState helper into this page to assist with developing the function for registering users.

The data object on line 73 above contains our component state.

The computed object contains immutable values.

The methods object contains functions that are to be run on this component.

When done with adding the code above, we are to include a couple of helpers on our template to save the entered user names, emails, and passwords when they are typed in.

Update your <form> element inside your main <template> element to this:

<form class="form-auth-small" @submit.prevent="submitting === false && registerUser()">
    <div class="form-group">
        <label for="signup-email" class="control-label sr-only">Name</label>
        <input v-model="name" type="text" class="form-control" id="signup-name" placeholder="Your name" />
    </div>
    <div class="form-group">
        <label for="signup-email" class="control-label sr-only">Email</label>
        <input v-model="email" type="email" class="form-control" id="signup-email” placeholder="Your email" />
    </div>
    <div class="form-group">
        <label for="signup-password" class="control-label sr-only">Passwords</label>
        <input v-model="password” type="password" class="form-control" id="signup-password" placeholder="Password" />
    </div>
    <button type="submit" class="btn btn-primary btn-1g btn-block">REGISTER</button>
</form>

In case you’re wondering, the aim of the “submitting” data property is a boolean-value property that indicates if an API call is currently active. When active, further API calls are blocked from occurring to reduce the load on the server.

With what we’ve done above, if you enter the correct values and hit the “REGISTER” button, there will be a POST request sent to the server attempting to register a user and create an authentication session for that user.

Give it a shot and observe the activity on your browsers Network panel when inspecting:

You’ve done a good job making it this far. We have successfully consumed the registration endpoint. The next thing we want to do is to save the returned data to our user.js store.

The returned data looks like this:

All we need from this object is the token alongside the user object. Let’s head over to the user.js store and make some modifications:

import { defineStore } from 'pinia';

const useUserStore = defineStore('user', {
    state: () => ({
        token: localStorage.getItem('token') || null,
        storedUser: localStorage.getItem('user') || null
    }),
    getters: {
        user: state => {
            if (!!state.storedUser) {
                return JSON.parse(state.storedUser);
            }
            return state.storedUser;
        },
        userIsAuth: state => !lstate.token
    },
    actions: {
        storeLoggedInUser(token, user) {
            const _this = this;

            // Save the token to localStorage
            localStorage.setItem('token', token);

            // Save the user to localStorage
            const stringifiedUser = JSON.stringify (user);
            localStorage.setItem('user', stringifiedUser);

            // Save the token and user to the store ktate
            _this. token = token;
            _this.storedUser = stringifiedUser;
        }
    }
});

export default useUserStore;

Let us go back to the Register.vue file and implement the “storeLoggedInUser” action we just added to the user.js store.

Your Register.vue page should look like this now:

<script>
    import axios from 'axios';
    import useGeneralStore from '../store/general';
    import useUserStore from '../store/user';
    import { mapActions, mapState } from 'pinia';
    export default {
        data: () = {
            return {
                name: '', 
                email: '',
                password: '',
                submitting: false
            }
        },
        computed: {
            ...mapState(useGeneralStore, [
                'API_URL',
            ])
        },
        methods: {
            ...mapActions(useUserStore, [
                'storeLoggedInUser',
            ])
            registerUser() {
                const _this = this;
                _this.submitting = true;
                axios.post(`${_this.API_URL}register`, {
                    name: _this.name,
                    email: _this.email,
                    password: _this. password
                }).then(RESPONSE => {
                    const token = RESPONSE.data.authorisation.token;
                    const user = RESPONSE.data.user;
                    _this.storeLoggedInUser(token, user);
                    alert(RESPONSE.data.message);
                }).catch(ERROR => {
                    console.log(ERROR);
                    alert(ERROR.response.data.message);
                }).then(() => {
                    _this.submitting = false;
                });
            }
        }
    }
</script>

With this, when a user successfully registers, a session containing the user’s token and details is created and saved in the user.js store via the “storeLoggedInUser” action we created.

Head over to your browser and check this new implementation out. This time open up the Vue.js Devtools panel. Select the Pinia tab and then create a user with this tab still opened up. Once the user registration is successful, you will see that your store has been updated with the user’s token and details seen below:

NOTE: If you do not see the store update, ensure you close the alert box indicating successful user registration.

The last thing we want to do in this chapter is to automatically redirect users to the dashboard as soon as their registration is successful. To do this, we will be making use of an object called watch on our Register.vue page.

This property works by “watching” for value changes on a particular data or computed property and acts immediately.

In our case, we are going to “watch” for changes in the “userIsAuth” getter property on our user.js store. Once the boolean value of this property changes to true, users are redirected to their dashboards instantly.

To do this, on our Register.vue page, update the computed object to this below:

// ...Preceeding code
computed: {
    ...mapState(useGeneralStore, [
        'API_URL'
    ]),
    ...mapState(useUserStore, [
        'userIsAuth'
    ])
},
// ...Succeeding code

Now add a watch object just after the computed object. Also, add the proceeding code within it shown below:

// ...Preceeding code
computed: {
    ...mapState(useGeneralStore, [
        'API_URL'
    ]),
    ...mapState(useUserStore, [
        'userIsAuth'
    ])
},
watch: {
    userIsAuth() {
        /* 
            By default the userIsAuth value is false, On 
            successful registration, after the storeLoggedInUser 
            method is called, the userIsAuth value is set to 
            true and this watch property is invoked. The code 
            below will then run.
        */
    }
},
methods: {// ...Succeeding code

Feel free to remove the alert function on successful user registration as it isn’t particularly necessary.

With that completed, you are now able to register users in Vue.js. There’s nothing more to it. With what you’ve learned you can make any POST request to any API at all no matter how sophisticated it may be.

That being said, I have decided that the next chapter will be an exercise chapter since what is to be done is nothing much different from what we’ve done in this chapter. I want you to consume the login endpoint: localhost:8000/api/login if your backend service is running locally or vue-training-backend.danikoko.com/api/login if you want to use a live server.

The parameters required are “email” and “password”.

The response from the API is the same as it is with the registration endpoint so you should implement the “storeLoggedInUser” action similarly.

Good luck with the exercise.

Your time starts now 🙂

Consuming Endpoints: User Login (Exercise)

Exercise solution:

github.com/Danikoko/vue-training-frontend/t..

Routes Configuration

In this chapter, we want to restrict the app dashboard (HomePage in the index.js route file) to authenticated users and block unauthenticated users from visiting the route.

We will also block authenticated users from accessing the login and register routes.

Vue Router provides us with a helper to achieve this - a method called beforeEnter which we will add to each route in the index.js file inside the router folder.

We are going to import the user.js store and then update the index.js routes array so each route has a beforeEnter method attached to it. Your code should look like this:

import { createRouter, createWebHistory } from 'vue-router';

import HomePage from '../pages/Hone. vue';
import LoginPage from '../pages/Login.vue';
import RegisterPage from '../pages/Register.vue';
import useUserStore from '../store/user';

const routes = [
    {
        path: '/',
        component: HomePage,
        beforeEnter: (to, from, next) => {
            // Install the user store
            const userStore = useUserStore();
            // Redirect if user is not authenticated
            if (userStore.userIsAuth === false) {
                return next('/login');
            }
            // Allow route entry if user is authenticated
            return next();
        }
    },
    {
        path: '/login',
        component: LoginPage,
        beforeEnter: (to, from, next) => {
            // Install the user store
            const userStore = useUserStore() ;
            // Redirect if user is authenticated
            if (userStore.userIsAuth === true) {
                return next('/');
            }
            // Allow route entry if user is not authenticated
            return next();
        }
    },
    {
        path: '/register',
        component: RegisterPage,
        beforeEnter: (to, from, next) => {
            // Install the user store
            const userStore = useUserStore() ;
            // Redirect if user is authenticated
            if (userStore.userIsAuth === true) {
                return next('/');
            }
            // Allow route entry if user is not authenticated
            return next();
        }
    }
];

With this, you can only access the dashboard when a user is logged in. Also, the login and register pages are only accessible when a user is not logged in.

What we’ve done is pretty much the same concept applied to complex router guards in top applications. Routes are guarded by roles, permissions, user statuses, etc. with the beforeEnter method.

Logging Users Out

Since users can successfully log in, it’s only right there should be a way for them to log out too.

To do this, let’s start implementation on the user.js store. Add a new action called “logoutUser” and its corresponding code:

// ...Preceeding action
logoutUser() {
    const _this = this;

    // Delete the token from localStorage
    localStorage.removeItem('token');

    // Delete the user from localStorage
    localStorage.removeItem('user');

    // Delete the token and user from the state
    _this.token = null;
    _this.storedUser = null;
}
// ...Succeeding action

Now go to the Home.vue page and completely replace the <script> element and its content with the code shown below:

<script>
    import axios from 'axios';
    import useInventoryStore from '../store/inventory';
    import useGeneralStore from '../store/general';
    import useUserStore from '../store/user';
    import { mapState, mapActions } from 'pinia';
    export default {
        data: () => {
            return {
                loggingOut: false
            }
        },
        computed: {
            ...mapState(useInventoryStore, []),
            ...mapState(useGeneralStore, [
                'API_URL'
            ]),
            ...mapState(useUserStore, [
                'token',
                'userIsAuth'
            ])
        },
        watch: {
            userIsAuth() {
                /* 
                    At this point the userIsAuth value is true.
                    On successful logout, after the storeLoggedInUser
                    method is called, the userIsAuth value is 
                    set to false and this watch property is invoked.
                    The code below will then run.
                */
                this.$router.push('/login');
            }
        },
        methods: {
            ...mapActions(useInventoryStore, []),
            ...mapActions(useUserStore, [
                'logoutUser'
            ]),
            logUserOut() {
                const _this = this;
                _this.loggingOut = true;
                axios.post(`${_this.API_URL}logout`, {}, {
                    headers: {
                        Authorization: `Bearer ${_this.token}`
                    }
                }).then(RESPONSE => {
                    alert(RESPONSE.data.message);
                }).catch(ERROR => {
                    console.log(ERROR);
                    alert(ERROR.response.data.message);
                }).then(() => {
                    _this.logoutUser();
                    _this.loggingOut = false;
                });
            }
        }
    }
</script>

Finally, on the <template> element, search for the following and replace

  1.     <li>
            <!-- 
                REPLACE
                <router-link to="/login" class="icon-menu">
                    <i class="icon-power"></i>
                </router-link>
                WITH 
            -->
            <a 
            @click.prevent="loggingOut === false && logUserOut()" 
            href="#"
            class="icon-menu"
            >
                <i class="icon-power"></i>
            </a>
            <!---->
        </li>
    
  2.     <li>
            <!-- 
                REPLACE
                <a href="#"><i class="icon-power"></i>Logout</a> 
                WITH 
            -->
            <a 
            @click.prevent="loggingOut === false && logUserOut()" 
            href="#"
            >
                <i class="icon-power"></i>
                Logout
            </a>
            <!---->
        </li>
    

Now that this is done. Clicking on the power button shown below triggers the logUserOut method.

The logUserOut method makes an API call to revoke the authorization token for the authenticated user. Once that has been done, the user details and token are removed from the user.js store.

Remember we have a “watcher” actively listening to the “userIsAuth” property on this same page. Once the value is changed to false (indicating the user details and token have been removed from the user.js store), the user is redirected to the login page instantly.

Displaying Inventories

Welcome to the final chapter of this tutorial.

In this chapter we will cover the principles involved in fetching data from APIs, storing the data in stores, and rendering these data to the currently authenticated user.

We start by modifying the inventory.js store to properly handle all inventories that will be fetched by our API call. Update the code on the inventory.js store like so:

import { defineStore } from 'pinia';

const useInventoryStore = defineStore('inventory', {
    state: () => ({
        allInventories: localStorage.getItem('allInventories') || null
    }),
    getters: {
        inventories: state => {
            return (
                state.alllnventories
                ? JSON.parse(state.allInventories)
                : []
            );
        }
    },
    actions: {
        storeInventories(allInventories) {
            const stringifiedData = JSON.stringify(allInventories);
            localStorage.getItem('allInventories', allInventories)
            this.allInventories = stringifiedData;
        }
    }
});

export default useInventoryStore;

On our Home.vue page, we will be importing the inventories state and storeInventories action from the inventory.js store:

// ...Preceeding code
computed: {
    ...mapState(useInventoryStore, [
        'inventories'
    ]),
// ...Succeeding computed properties
// ...Preceeding code
methods: {
    ...mapState(useInventoryStore, [
        'storeInventories'
    ]),
// ...Succeeding methods

Finally, we’ll define a method for fetching all inventory items and invoke this method in a mounted lifecycle which we’ll create. Add the fetchInventory method and the mounted lifecycle like done below:

// ...Preceeding code
methods: {
    // ...Preceeding methods
    fetchInventory() {
        const _this = this;
        axios.get(`${_this.API_URL}inventories`, {
            headers: {
                Authorization: `Bearer ${_this.token}`
            }
        })then(RESPONSE => {
            _this.storeInventories(RESPONSE.data.inventories);
        }).catch(ERROR => {
            console.log(ERROR);
            alert(ERROR.response.data.message);
        }).then(() => {
            //
        });
    }
},
mounted() {
    this.fetchInventory();
}

With this, we call the method for fetching inventories each time we visit the Home.vue page. The data fetched is saved to our Pinia store for global usage across our application. Inspect your browser’s Network and Vue (Pinia tab) tabs to observe changes from successful login to refreshing the Home page.

The next thing to do is to render this fetched data in a loop on our Home.vue page. On the component, look for the <table> element and replace its content with these:

<!-- Preceeding markup -->
<table class="table table-hover js-basic-example dataTable table-custom mb-0">
    <thead class="thead-dark">
        <tr>
            <th>Date Added</th>
            <th>Name</th>
            <th>Type</th>
            <th>Description</th>
            <th>Price</th>
            <th>Status</th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="(item, index) in inventories" :key="index">
            <td>{{ item.created_at }}</td>
            <td>{{ item.name }}</td>
            <td>{{ item.type }}</td>
            <td>{{ item.description }}</td>
            <td>${{ item.price }}</td>
            <td>
                <span v-if="item.status == 1" class="badge badge-success">sold</span>
                <span v-else class="badge badge-danger">unsold</span>
            </td>
        </tr>
    </tbody>
</table>
<!-- Succeeding markup -->

This is how looping arrays is done in Vue.js. Lines 352 and 353 above show conditional rendering in Vue.js (in this case if item.status == 1) - meaning an inventory item is sold).

Have a look at the home page on your browser - you should see your inventory beautifully rendered on the table:

Bonus

Our bonus lecture will be channeled toward determining the total price of items in our inventory:

This value should be the exact result of the sum of the values in the Price column in this table:

From my little math skills, the total price should be 33000. Oh sorry, 30000. You know what let me just use a calculator 😂

Okayyyy… The total price is 34000 - but instead of guessing, we can make Vue.js do heavy lifting and calculations for us using a computed property. Let me show you how. On your Home.vue page, add the totalPrice computed property:

// ...Preceeding code
computed: {
    // ...Preceeding computed properties
    totalPrice() {
        let value = 0;
        this.inventories.forEach(item => {
            value += Number(item.price)
        });
        return value;
    }
}
// ...Succeeding code

Now let’s render it - search for the <div> element displaying the Total Amount and replace the content there with this:

<!-- Preceeding markup -->
<div class="clearfix">
    <div class="float-left">
        <i class="fa fa-2x fa-dollar text-col-blue"></i>
    </div>
    <div class="number float-right text-right">
        <h6>Total Amount</h6>
        <span class="font700">${{ totalPrice }}</span>
    </div>
</div>
<!-- Succeeding markup -->

Save the changes and your Total Amount should render the correct value now:

With this, we have come to the end of this short tutorial on building applications with Vue.js. You can use this as a reference for your Vue.js projects. I’m excited about what you’re going to build (you can mail me links to your work).

In the meantime, I'd like you to attempt changing “Christy Wert” shown below to the currently logged-in user’s name dynamically.

Use what you’ve learned so far in importing stores and their values. Good luck 💯

Final Words

All we’ve done up to this point can be viewed here.

You can log in with these credentials:

EMAIL: danikoko@example.com

PASSWORD: password

Feel free to create test accounts.

Have fun building.