Frontend Development

Creating Multi-step Form With Vuejs and Vuex Part 2

vuejs multi-step form

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.

2.7 3 votes
Article Rating

What's your reaction?

Excited
0
Happy
0
Not Sure
1
Confused
4

You may also like

Subscribe
Notify of
guest

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Nazmi Sefa Kelleci
Nazmi Sefa Kelleci
4 years ago

what happens when the client refreshes page during the process ?

what is the best practice for not losing any data in state ?

Robert Obiri
3 years ago
Reply to  WebMobTuts

ok please how do i submit all the data to my backend node or firebase api. Thank you