In the previous part of this series i showed how to created our app and created the form components and navigate between them using Vue router.
In this part we will continue this series if you need to check the last part click here. In this part we will save the form data into Vuex .
Vuex act as a central state manager for Vuejs so all your app data is contained within a big object and any updates that happened to that object is reflected in real time and this is what the term reactivity means.
Creating the Vuex Store
Open up store/index.js and add the below the code:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store( { state: { }, mutations: { } } )
Here i created a new Store. A typical store should have at least two objects (state, mutations). State is where you add your form data so each form field will be mapped to state object like this:
{ state: { firstname: "" } }
Mutations is just functions that you use to update state objects. Note that you can’t update state directly using syntax like this:
this.$store.state.firstname = "John" // This is wrong
But to update the state you need to create a mutation and then commit the mutation like this:
{ mutations: { setFirstname(state, value) { state.firstname = value } } } // to update state commit('setFirstname', 'John')
Let’s update our app.js and add the store as shown in the below code:
import 'babel-polyfill' import Vue from 'vue' import VueRouter from 'vue-router' import App from './components/App.vue' import store from './store/index' Vue.use(VueRouter); import { routes } from './routes/index' let router = new VueRouter({ mode: 'hash', routes, store }); new Vue({ el: "#app", router, store, render: h => h(App) })
Now let’s fill the store with our form data so open store/index.js and add the below code:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store( { state: { firstname: "", lastname: "", email: "", password: "", gender: "", phone: "", birthdate: "", country: "", state: "", city: "", street: "", zipcode: "", facebook: "", google: "", twitter: "", linkedin: "", instagram: "" }, mutations: { setFirstname(state, value) { state.firstname = value }, setLastname(state, value) { state.lastname = value }, setEmail(state, value) { state.email = value }, setPassword(state, value) { state.password = value }, setGender(state, value) { state.gender = value }, setPhone(state, value) { state.phone = value }, setBirthdate(state, value) { state.birthdate = value }, setCountry(state, value) { state.country = value }, setState(state, value) { state.state = value }, setCity(state, value) { state.city = value }, setStreet(state, value) { state.street = value }, setZipcode(state, value) { state.zipcode = value }, setFacebook(state, value) { state.facebook = value }, setGoogle(state, value) { state.google = value }, setTwitter(state, value) { state.twitter = value }, setLinkedin(state, value) { state.linkedin = value }, setInstagram(state, value) { state.instagram = value }, } } )
Also we need to modify our components and set the store:
components/steps/FirstStep.vue
<template> <div class="text-center border border-light p-5"> <p class="h4 mb-4">Basic Details</p> <div class="form-row mb-4"> <div class="col"> <!-- First name --> <input type="text" id="firstname" name="firstname" class="form-control" placeholder="First name" v-model="firstname"> </div> <div class="col"> <!-- Last name --> <input type="text" id="lastname" name="lastname" class="form-control" placeholder="Last name" v-model="lastname"> </div> </div> <!-- Email --> <input type="email" id="useremail" name="email" class="form-control mb-4" placeholder="E-mail" v-model="email"> <!-- Password --> <input type="password" id="password" name="password" class="form-control" placeholder="Password" v-model="password" aria-describedby="defaultRegisterFormPasswordHelpBlock"> <small id="defaultRegisterFormPasswordHelpBlock" class="form-text text-muted mb-4"> At least 8 characters and 1 digit </small> <!-- Gender --> <select class="browser-default custom-select mb-4" name="gender" v-model="gender"> <option value="" disabled selected>Choose gender</option> <option value="1">Male</option> <option value="2">Female</option> </select> <input type="text" id="phone" name="phone" class="form-control mb-4" placeholder="Phone" v-model="phone"> <input type="date" id="birthdate" name="birthdate" class="form-control mb-4" placeholder="Birth date" v-model="birthdate"> <buttons></buttons> </div> </template> <script> import Buttons from './Buttons.vue'; export default { components: { Buttons }, computed: { firstname: { get() { return this.$store.state.firstname }, set(value) { this.$store.commit('setFirstname', value) } }, lastname: { get() { return this.$store.state.lastname }, set(value) { this.$store.commit('setLastname', value) } }, email: { get() { return this.$store.state.email }, set(value) { this.$store.commit('setEmail', value) } }, password: { get() { return this.$store.state.password }, set(value) { this.$store.commit('setPassword', value) } }, gender: { get() { return this.$store.state.gender }, set(value) { this.$store.commit('setGender', value) } }, phone: { get() { return this.$store.state.phone }, set(value) { this.$store.commit('setPhone', value) } }, birthdate: { get() { return this.$store.state.birthdate }, set(value) { this.$store.commit('setBirthdate', value) } } } } </script>
components/steps/SecondStep.vue
<template> <div class="text-center border border-light p-5"> <p class="h4 mb-4">Address Details</p> <input type="text" id="country" name="country" class="form-control mb-4" placeholder="Country" v-model="country"> <input type="text" id="state" name="state" class="form-control mb-4" placeholder="State" v-model="state"> <input type="text" id="city" name="city" class="form-control mb-4" placeholder="City" v-model="city"> <div class="form-row mb-4"> <div class="col"> <input type="text" id="street" name="street" class="form-control" placeholder="Street Address" v-model="street"> </div> <div class="col"> <input type="text" id="zipcode" name="zipcode" class="form-control" placeholder="ZipCode" v-model="zipcode"> </div> </div> <buttons></buttons> </div> </template> <script> import Buttons from './Buttons.vue'; export default { components: { Buttons }, computed: { country: { get() { return this.$store.state.country }, set(value) { this.$store.commit('setCountry', value) } }, state: { get() { return this.$store.state.state }, set(value) { this.$store.commit('setState', value) } }, city: { get() { return this.$store.state.city }, set(value) { this.$store.commit('setCity', value) } }, street: { get() { return this.$store.state.street }, set(value) { this.$store.commit('setStreet', value) } }, zipcode: { get() { return this.$store.state.zipcode }, set(value) { this.$store.commit('setZipcode', value) } } } } </script>
components/steps/ThirdStep.vue
<template> <div class="text-center border border-light p-5"> <p class="h4 mb-4">Social Links</p> <input type="text" id="facebook" name="facebook" class="form-control mb-4" v-model="facebook" placeholder="Facebook"> <input type="text" id="google" name="google" class="form-control mb-4" v-model="google" placeholder="Google"> <input type="text" id="twitter" name="twitter" class="form-control mb-4" v-model="twitter" placeholder="Twitter"> <input type="text" id="linkedin" name="linkedin" class="form-control mb-4" v-model="linkedin" placeholder="Linkedin"> <input type="text" id="instagram" name="instagram" class="form-control mb-4" v-model="instagram" placeholder="Instagram"> <buttons></buttons> </div> </template> <script> import Buttons from './Buttons.vue'; export default { components: { Buttons }, computed: { facebook: { get() { return this.$store.state.facebook }, set(value) { this.$store.commit('setFacebook', value) } }, google: { get() { return this.$store.state.google }, set(value) { this.$store.commit('setGoogle', value) } }, twitter: { get() { return this.$store.state.twitter }, set(value) { this.$store.commit('setTwitter', value) } }, linkedin: { get() { return this.$store.state.linkedin }, set(value) { this.$store.commit('setLinkedin', value) } }, instagram: { get() { return this.$store.state.instagram }, set(value) { this.$store.commit('setInstagram', value) } }, } } </script>
components/steps/Buttons.vue
<template> <div> <button class="btn btn-warning" @click="navigatePrev" type="button" v-show="togglePrevious()">Previous</button> <button class="btn btn-info" @click="navigateNext" type="button">{{ nextBtnTxt() }}</button> <transition name="modal"> <div class="modal-mask" v-show="showResult"> <div class="modal-wrapper"> <div class="modal-container"> <div class="modal-header"> Congrats </div> <div class="modal-body" v-html="result"> </div> <div class="modal-footer"> <slot name="footer"> <button class="modal-default-button btn btn-warning" @click="close"> close </button> </slot> </div> </div> </div> </div> </transition> </div> </template> <script> export default { data() { return { showResult: false, result: '' } }, methods: { nextBtnTxt() { if(this.$route.name == 'thirdStep') { return 'Finish'; } return 'Next'; }, togglePrevious() { if(this.$route.name == 'firstStep') { return false; } return true; }, navigateNext() { if(this.$route.name == 'firstStep') { this.$router.push('/second-step/'); } else if(this.$route.name == 'secondStep') { this.$router.push('/third-step/'); } else { // handle saving of the form this.result = ` <p><strong>Firstname: </strong> ${this.$store.state.firstname}</p> <p><strong>Lastname: </strong> ${this.$store.state.lastname}</p> <p><strong>Email: </strong> ${this.$store.state.email}</p> <p><strong>Gender: </strong> ${this.$store.state.gender}</p> <p><strong>Phone: </strong> ${this.$store.state.phone}</p> <p><strong>Country: </strong> ${this.$store.state.country}</p> <p><strong>State: </strong> ${this.$store.state.state}</p> <p><strong>Facebook: </strong> ${this.$store.state.facebook}</p> <p><strong>Twitter: </strong> ${this.$store.state.twitter}</p> ` this.showResult = true; } }, navigatePrev() { if(this.$route.name == 'thirdStep') { this.$router.push('/second-step/'); } else if(this.$route.name == 'secondStep') { this.$router.push('/first-step/'); } }, close() { this.showResult = false } } } </script> <style scoped> .modal-mask { position: fixed; z-index: 9998; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, .5); display: table; transition: opacity .3s ease; } .modal-wrapper { display: table-cell; vertical-align: middle; } .modal-container { width: 70%; margin: 0px auto; padding: 20px 30px; background-color: #fff; border-radius: 2px; box-shadow: 0 2px 8px rgba(0, 0, 0, .33); transition: all .3s ease; font-family: Helvetica, Arial, sans-serif; } .modal-header h3 { margin-top: 0; color: #42b983; } .modal-body { margin: 20px 0; } .modal-default-button { float: right; } .modal-enter { opacity: 0; } .modal-leave-active { opacity: 0; } .modal-enter .modal-container, .modal-leave-active .modal-container { -webkit-transform: scale(1.1); transform: scale(1.1); } </style>
As shown in the code above we have make of the two way binding with v-model then we used the store data and create a computed property for each field and then the v-model will update the field accordingly. Finally in the Buttons.vue component when we click in the finish button then we display a summary of all the data that we selected and this is the benefit of the Vuex store. Here it’s up to you to save the data using remote api call and send it to the server.
Conclusion
In this series we learned how to create a multi-step form using the benefits of Vuejs components. And also learned how to bundle our view javascript files using webpack. And how to save the into the store using Vuex. You can further extend this tutorial and add the validation for the form and save the data into database.
what happens when the client refreshes page during the process ?
what is the best practice for not losing any data in state ?
In real world project this form will be connected to an Api that fetch data from server or database
ok please how do i submit all the data to my backend node or firebase api. Thank you