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 - If the
POST
request was successful (Statuscode 2XX), a success message 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
- sass-loader node-sass style-loader for SCSS Support
- Axios for making Promise based HTTP requests (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
toLogin
- 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
- Replace the import in the script from
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
- We need a property for the Error Message. This will be a STRING (if the property is null, no Error Message 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. - We need a mutation for when the login request has finished. This Mutation will
- Set the
loggingIn
property tofalse
- Set the
loginError
property to the parameter passed to the Mutation (null
or a STRING) - Set the
loginSuccessful
property totrue
if there was no error,false
if there was
- Set the
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 - 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 (withnull
as parameter because no error occurred) - If the request was successful, the
loginStop
Mutation will be committed (witherror.response.data.error
as parameter, to pass the error from the backend)
- If the request was successful, the
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 - We need an element to display error messages (if
loginError
is notnull
) - 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 - We need to define the
loginSubmit
method as used in the template. This method should dispatch thedoLogin
action with bothemail
and thepassword
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.
Thanks for this! Tip: it’s probably a better idea to store the login logic entirely within the Logic component rather than putting it in the store. The store is best used for distributed logic that needs to be used in many components across the app.
Hello Michael,
thank you for the first comment on my Blog
You are probably right that the login logic does not have to be stored in the Vuex store. I did so because
1. I wanted to show the concept of the Vuex state management
2. If you later need the error/success message or the loggingIn value on another place, you can get it easily from the store
Cheers
Hey,
Why do you need to ‘mapActions’ when later on you do a ‘dispatch’ from $store?
Seems to me like the whole mapActions is useless.
This wouldn’t work?
this.doLogin({ email: this.email, password: this.password })
Thx.
Hello Adam,
thank you for your comment. You are absolutely right! I corrected it both in the post and in the github repository!
Cheers
Ich kann keineswegs widerstehen, diesen Artikel zu kommentieren. Sehr
gut geschrieben! Ich denke dieses ist eine tolle Site für Leute die bevorzugt vorzügliche Blogs durchlesen. Es ist natürlich nicht nur der Inhalt, den ich ansprechend finde, sondern insbesondere das Layout der Website.
Lieben Dank für die grosse Arbeit. Bitte mach so weiter.
Beste Grüße von Andrea aus Düsseldorf.
Hallo Andrea, vielen Dank für deinen netten Kommentar! Leider habe ich seit Oktober 2018 keinen weiteren Blog-Post veröffentlicht, da ich an einem eigenen Projekt arbeite, das ich hoffentlich bald veröffentlichen werde.
LG Marc aus München