About

The code is available in a Github repository

What we will be creating

In this post I will show you, how to create a Login Component in Vue.js.

To simulate a RESTful API to connect to, I will use reqres (more specific the POST /api/login route).

The Component we create should do the following

Let us log in by entering an E-Mail and a Password followed by submitting the form

While the POST request is sent to the backend, a spinner should be overlaying the form

request is sent to the backend, a the form If the POST request was successful ( Statuscode 2XX ), a success message should be displayed

request was successful ( ), a should be displayed If the POST request was not successful (Statuscode 4XX), the error message from the backend (response) should be displayed

How will we accomplish this

To accomplish this, a variety of technologies working together will be used, such as

Vue CLI for rapid Vue.js development

development sass-loader node-sass style-loader for SCSS Support

Support Axios for making Promise based HTTP requests (to the RESTful API)

(to the RESTful API) Vuex for state management

Install the Vue CLI

If you didn't install the Vue CLI yet, install it

sudo npm install -g @vue/cli

Create a new Project

Create a new Project and serve it

vue create <project-name> cd <project-name> npm run serve

Add additional packages

Add SCSS support and axios by installing them via npm

npm install sass-loader node-sass style-loader --save-dev npm install axios --save

Implement Vuex into the Application by using the Vue CLI (Version 3) which supports Vuex as a plugin.

vue add vuex

This will install the Vuex npm package aswell as configure the application ( src/main.js ) correctly to use Vuex and create a Store file in src/store.js

Adjust App and add Login Component

Add the Login Component src/components/Login.vue

Remove the HelloWorld Component in src/components/

Adjust the src/App.vue file Replace the import in the script from HelloWorld to Login Replace the template usage of <HelloWorld /> with <Login /> in the template Remove the <img /> tag in the template Remove the margin-top style in the styles

file

The App Component should look like this now

< template > < div id = "app" > < Login /> </ div > </ template > < script > import Login from './components/Login.vue' export default { name : 'app' , components : { Login } } </ script > < style > #app { font-family : 'Avenir' , Helvetica, Arial, sans-serif; -webkit-font-smoothing : antialiased; -moz-osx-font-smoothing : grayscale; text-align : center; color : #2c3e50 ; } </ style >

The Store

Before we edit the Login Component, I want to create the Vuex Store first.

State

We need a property to determine, if we are currently logging in (to display the spinner). This will be a BOOLEAN

(to display the spinner). This will be a We need a property for the Error Message . This will be a STRING (if the property is null, no Error Message will be displayed)

. This will be a (if the property is null, no will be displayed) We need a property to determine, if the login was successful (to display the Success Message). This will be a BOOLEAN

state: { loggingIn : false , loginError : null , loginSuccessful : false }

Getters

We don't need to generate Getters for accessing unmodified state properties.

...mapState can be used instead in the Components. (I will come back to this later).

Mutations

We need a mutation for when we start logging in, to set the loggingIn property of the state accordingly.

property of the state accordingly. We need a mutation for when the login request has finished. This Mutation will Set the loggingIn property to false Set the loginError property to the parameter passed to the Mutation ( null or a STRING ) Set the loginSuccessful property to true if there was no error, false if there was



mutations: { loginStart : state => state.loggingIn = true , loginStop : ( state, errorMessage ) => { state.loggingIn = false ; state.loginError = errorMessage; state.loginSuccessful = !errorMessage; } }

Actions

We need one Action which will

First of all commit the loginStart Mutation

the Send an axios.post ( import axios from 'axios' ) request with the data passed to the action as body to the API Endpoint If the request was successful, the loginStop Mutation will be committed (with null as parameter because no error occurred) If the request was successful, the loginStop Mutation will be committed (with error.response.data.error as parameter, to pass the error from the backend)

( ) request with the data passed to the as body to the API Endpoint

By using the Spread Operator you can merge the key-value pairs of one object into another (this is an ES6 feature). In this case you could also omit it by using loginData as the body but I wanted to mention this option.

actions: { doLogin({ commit }, loginData) { commit( 'loginStart' ); axios.post( 'https://reqres.in/api/login' , { ...loginData }) .then( () => { commit( 'loginStop' , null ) }) .catch( error => { commit( 'loginStop' , error.response.data.error) }) } }

Complete Store

import Vue from 'vue' import Vuex from 'vuex' import axios from 'axios' Vue.use(Vuex) export default new Vuex.Store({ state : { loggingIn : false , loginError : null , loginSuccessful : false }, mutations : { loginStart : state => state.loggingIn = true , loginStop : ( state, errorMessage ) => { state.loggingIn = false ; state.loginError = errorMessage; state.loginSuccessful = !errorMessage; } }, actions : { doLogin({ commit }, loginData) { commit( 'loginStart' ); axios.post( 'https://reqres.in/api/login' , { ...loginData }) .then( () => { commit( 'loginStop' , null ) }) .catch( error => { commit( 'loginStop' , error.response.data.error) }) } } })

Login Markup

We need a form holding two inputs (for E-Mail and Password both with v-model binding) and a button

binding) and a button We need an element to display error messages (if loginError is not null )

is not ) We need an element to display the success message (if loginSuccessful )

) We need an element to overlay and display a spinner (if loggingIn )

The advantage of using a form with @submit.prevent (instead of a click listener on the button) is, that you can submit the form either by clicking the button or by pressing ENTER when an input field is focussed.

Submitting the form will trigger a component method called loginSubmit , which will be defined in the js code of this Component.

< template > < div class = "login" > < div v-if = "loggingIn" class = "container-loading" > < img src = "/loading.gif" alt = "Loading Icon" > </ div > < p v-if = "loginError" > {{ loginError }} </ p > < p v-if = "loginSuccessful" > Login Successful </ p > < form @ submit.prevent = "loginSubmit" > < input type = "email" placeholder = "E-Mail" v-model = "email" > < input type = "password" placeholder = "Password" v-model = "password" > < button type = "submit" > Login </ button > </ form > </ div > </ template >

The loading.gif file can be found in the Github repository

Login Styling

There is not much to say about the styling. We installed all necessary packages to support scss.

You can of course use css aswell.

I scoped the styles to this Component so they won't affect the rest of the application.

< style scoped lang = "scss" > .login { border: 1px solid black; border-radius: 5px; padding: 1.5rem; width: 300px; margin-left: auto; margin-right: auto; position: relative; overflow: hidden; .container-loading { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; justify-content: center; align-items: center; background-color: rgba(0,0,0,.3); img { width: 2rem; height: 2rem; } } form { display: flex; flex-flow: column; *:not(:last-child) { margin-bottom: 1rem; } input { padding: .5rem; } button { padding: .5rem; background-color: lightgray; border: 1px solid gray; border-radius: 3px; cursor: pointer; &:hover { background-color: lightslategray; } } } } </ style >

Login Javascript

Data

We need one property called email and one called password to bind to the input fields. They should be empty per default.

data() { return { email : '' , password : '' } }

Computed

As mentioned in the Getters section of the Store, we can use ...mapState ( import { mapState } from 'vuex'; ) to get access to the properties of the state we need.

computed: { ...mapState([ 'loggingIn' , 'loginError' , 'loginSuccessful' ]) }

Methods

We need to get access to the doLogin action to dispatch it. We can use ...mapActions ( import { mapActions } from 'vuex'; ) for this

to dispatch it. We can use ( ) for this We need to define the loginSubmit method as used in the template. This method should dispatch the doLogin action with both email and the password in a javascript object

methods: { ...mapActions([ 'doLogin' ]), loginSubmit() { this .doLogin({ email : this .email, password : this .password }) } }

Complete Javascript

< script > import { mapState, mapActions } from 'vuex' ; export default { data() { return { email : '' , password : '' } }, computed : { ...mapState([ 'loggingIn' , 'loginError' , 'loginSuccessful' ]) }, methods : { ...mapActions([ 'doLogin' ]), loginSubmit() { this .doLogin({ email : this .email, password : this .password }) } } } </ script >

Test it

The reqres API Endpoint we use, will return successfully if you type any E-Mail and any Password but will fail if you leave one or both blank.

I hope this helped you. If it did, make sure to follow me on social media for upcoming posts.

Also make sure to check out my next post about persisting the token, we receive from the backend upon logging in which builds on top of the code discussed here.