Multi-step form using Vue, Vuex, Computed Setters And v-model

by darkylmnx

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<div id="app">
  <h1>Easily mail your package</h1>
  <p>
    <progress max="4" :value="percent"></progress>
   </p>
  <component :is="step_cmp"></component>
</div>

<script type="text/template" id="cmp-infos">
<form @submit.prevent="nextStep">
	<h3>Who are you sending to</h3>
  <p><input placeholder="firstname" v-model.lazy="firstname" required/></p>
  <p><input placeholder="lastname" v-model.lazy="lastname" required/></p>
  <button>next</button>
</form>
</script>

<script type="text/template" id="cmp-what">
  	<form @submit.prevent="nextStep">
    	<h3>what are you sending to : {{ $store.state.form.firstname }}</h3>
    	<p><input placeholder="item" v-model.lazy="item" required/></p>
      <button type="button" @click="prevStep">prev</button>
      <button>next</button>
   </form>
</script>

<script type="text/template" id="cmp-where">
  	<form @submit.prevent="nextStep">
    	<h3>where are you sending : {{ $store.state.form.item }}</h3>
    	<p><input placeholder="enter address" v-model.lazy="to" required/></p>
      <button type="button" @click="prevStep">prev</button>
      <button>next</button>
    </form>
</script>

<script type="text/template" id="cmp-final">
<form @submit.prevent="send">
	<h3>please confirm : </h3>
  <p>
  	{{ $store.state.form.firstname }}
    {{ $store.state.form.lastname }}
    <br/>
    {{ $store.state.form.item }}
    <br/>
    {{ $store.state.form.to }}
  </p>
  <button>confirm</button>
</form>
</script>

CSS

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

#app {
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

h1, h2 {
  font-weight: bold;
}

Vue

const STEPS = {
	INFOS: 'INFOS',
  WHAT: 'WHAT',
  WHERE: 'WHERE',
  FINAL: 'FINAL'
}

// infos component
Vue.component('cmp-infos', {
	template: "#cmp-infos",
  
  computed: {
  	firstname: {
    	get() {
      	return this.$store.state.form.firstname
      },
      
      set(firstname) {
      	this.$store.commit('SET_FORM', {
        	firstname
        })
      }
    },
    lastname: {
    	get() {
      	return this.$store.state.form.lastname
      },
      
      set(lastname) {
      	this.$store.commit('SET_FORM', {
        	lastname
        })
      }
    }
  },
  
  methods: {
  	nextStep() {
    	this.$store.commit('SET_STEP', STEPS.WHAT)
    }
  }
})

Vue.component('cmp-what', {
	template: '#cmp-what',
  
  computed: {
  	item: {
    	get() {
      	return this.$store.state.form.item
      },
      
      set(item) {
      	this.$store.commit('SET_FORM', {
        	item
        })
      }
    }
  },
  
  methods: {
  	prevStep() {
    	this.$store.commit('SET_STEP', STEPS.INFOS)
    },
  	nextStep() {
    	this.$store.commit('SET_STEP', STEPS.WHERE)
    }
  }
})

Vue.component('cmp-where', {
	template: '#cmp-where',
  
  computed: {
  	to: {
    	get() {
      	return this.$store.state.form.to
      },
      
      set(to) {
      	this.$store.commit('SET_FORM', {
        	to
        })
      }
    }
  },
  
  methods: {
  	prevStep() {
    	this.$store.commit('SET_STEP', STEPS.WHAT)
    },
  	nextStep() {
    	this.$store.commit('SET_STEP', STEPS.FINAL)
    }
  }
})

Vue.component('cmp-final', {
	template: '#cmp-final',
  methods: {
  	send() {
    	// do what you want like api calls and reset the form
      alert('thanks')
    }
  }
})

const store = new Vuex.Store({
	state: {
  	step: STEPS.INFOS,
    form: {
    	firstname: '',
      lastname: '',
      item: '',
      to: ''
    }
  },
  mutations: {
  	SET_FORM(state, payload) {
    	// merge previous state with new state
    	state.form = Object.assign({}, state.form, payload)
    },
   ...