How to build Multi Step Form in Vue3
Published: 2022-11-20 10:09:38

Hey folks! Hope you are doing well. This blog is about creating a multi step form in Vue.js with help of slots. 
If you wanna skip the read or directly import the component here's the link
https://github.com/yogeshgalav/vue-multi-step-form
https://www.npmjs.com/package/vue-multi-step-form

## Slots
Slots in simple language are used to inherit or include code from parent component were it will be used. 
Hence you can decide according to your needs what code you would like to show or embed in child component directly from parent component. 
Hence you have a dynamic component with dynamic html or template or text.
Mostly we use slots when we want to pass html or template to child component because passing it via props and then embedding it wouldn't be an ideal thing to do.   
If you want to study more here is the link to official docs:
https://vuejs.org/guide/components/slots.html

## Features
Now back to the Multi Step Form
Here are some features that we intend to implement
1. It should support n number of steps.
2. It should jump to next step when enter key pressed or submit button pressed.
3. Header and Footer should be modifiable via slots as well.
4. Step can be skippable.
5. Prevent next step when current step is invalid.
6. Form could be submit by html action attribute as well.

## Architecture
Here's how our form will look in html
```js
<form>
<div id="step1">
<input type="text" name="phone_no">
</div>
<div id="step2">
<input type="text" name="otp">
</div>
<div id="step3">
<input type="text" name="first_name">
<input type="text" name="last_name">
</div>
</form>
```

And here's how our form will look in vue

_LoginPage.vue_
```js
<template>
  <MultiStepForm>
    <template #step1>
      <input type="text" name="phone_no">
    </template>
    <template #step2>
      <input type="text" name="otp">
    </template>
    <template #step3>
      <input type="text" name="first_name">
      <input type="text" name="last_name">
    </template>
  </MultiStepForm>
</template>

```

_MultiStepForm.vue_
```js
<template>
  <form>
    <slot name="step1" />
    <slot name="step2" />
    <slot name="step3" />
  </form>
</template>
```

## Implementation
**Feature 1**
For our component to support 'N' number of steps we just need an array to loop over. That array we will pass from parent component via props.

_MultiStepForm.vue_
```js
<template>
  <form>
    <div 
     v-for="(step, index) in steps" 
     :key="index" 
     :id="'step'+(index+1)" 
     v-show="activeStepIndex===index"
     class="vue-form-step">

       <slot :name="'step'+(index+1)" />

     </div>
  </form>
</template>
```
```js
const props = defineProps(['steps']);
let activeStepIndex = ref(0);
```

**Feature 2**
For moving on to next step in form we just need to increase 'activeStepIndex' on submit button press.

_MultiStepForm.vue_
```js
  <form @submit.prevent="submitStep">
    <div 
     v-for="(step, index) in steps" 
     :key="index" 
     :id="'step'+(index+1)" 
     v-show="activeStepIndex===index"
     class="vue-form-step">

       <slot :name="'step'+(index+1)" />

     </div>
    <button type="submit">Next</button>
  </form>
```
```js
function submitStep(){
    activeStepIndex.value++;
}
```

**Feature 3**
Now we want to put some heading or design to Every step or Some hidden input field or if we wish to modify design of footer or submit button then we can implement via header and footer slot.

_MultiStepForm.vue_
```js
  <form @submit.prevent="submitStep">
    <slot name="header" />
    
    <div 
     v-for="(step, index) in steps" 
     :key="index" 
     :id="'step'+(index+1)" 
     v-show="activeStepIndex===index"
     class="vue-form-step">

       <slot :name="'step'+(index+1)" />

     </div>

    <slot name="footer">
      <button type="submit">Next</button>
    </slot>
  </form>
```
Now here we have provided default html to footer slot, Means if we doesn't mention footer slot in parent component, submit button will be available by default else we need to implement it in parent.

_LoginPage.vue_
```js
<template>
  <MultiStepForm :steps="steps">
    <template #header>
      <h1>Get Started -></h1>
    </template>
    <template v-slot:footer>
      <button class="btn btn-primary" 
       type="submit">Get OTP</button>
    </template>
    //steps slots
  </MultiStepForm>
</template>
```

**Feature 4**
Suppose if you have a long form and you want to skip some particular step based dynamically on some logic then we can do that by providing some data inside 'steps' props 

_LoginPage.vue_
```js
const steps= reactive([
    {'step_no':1,'step_valid':false,'step_skip':false},
    {'step_no':2,'step_valid':false,'step_skip':true},
    {'step_no':3,'step_valid':false,'step_skip':false},
]);
```
Also now we have prepared the data we need to implement the logic to skip the step. Also we need to take care we don't do any action if it's the final step.

_MultiStepForm.vue_
```js
const emit = defineEmits(['onComplete']);
function submitStep(){
  if(activeStepIndex.value === props.steps.length){
    //final step
    emit('onComplete');
    return false;
  }
  activeStepIndex.value++;
  while(props.steps[activeStepIndex.value].step_skip === true){
    activeStepIndex.value++;
  }
}
```

**Feature 5**
Looks like we have implemented most of the feature required for multi step form, Now let's take things to next level where we want to check if current step is valid. And if it is invalid then we show some form error and restrict user to go to the next step.
Remember we have used 'step_valid' inside step object, let's use it.

_MultiStepForm.vue_
```js
const emit = defineEmits(['onComplete','valdiateStep']);
function submitStep(){
  if(!props.steps[activeStepIndex.value].step_valid){
    emit('valdiateStep', stepIndex);
    return false;
  }
  //increase activeStepIndex
}
defineExpose({ submitStep });
```
_LoginPage.vue_
```js
<template>
  <MultiStepForm
  ref="multiStepForm"
  :steps="steps"
  @onComplete="submitForm"
  @validateStep="validateStep">
   //slots
  </MultiStepForm>
</template>
```
```js
let multiStepForm = ref(null);
function validateStep(stepIndex){
  //run validation of step
  //if step is valid then
  steps[stepIndex].step_valid=true;
  multiStepForm.value.submitStep();
  //else show errors
}
function submitForm(){
  //api call to submit all data via post request
  //redirect to somewhere
}
```
If you are facing issues while implementing validation library then we have simple validation package to fit your needs have a look.
https://www.npmjs.com/package/vue-nice-validate

**Feature 6**
Now coming onto our last step is to submit form via old way i.e. 
via html action and method if action prop is provided from parent.
And here's how our MultiStepForm component will look like

_MultiStepForm.vue_
```js
<form :id="id" :action="action" :method="method">
```
```js
const props = defineProps(['steps','id','action','method']);
```
```js
function submitStep(){
  if(!props.steps[activeStepIndex.value].step_valid){
    emit('valdiateStep', stepIndex);
    return false;
  }
  let isLastStep = (activeStepIndex.value === props.steps.length);
  if(isLastStep && props.action){
    submitForm();
    return true;
  }
  if(isLastStep){
    emit('onComplete');
    return true;
  }
  activeStepIndex.value++;
  while(props.steps[activeStepIndex.value].step_skip===true){
    activeStepIndex.value++;
  }
}
function submitForm(){
  document.getElementById(props.id).submit();
}
```

I hope you enjoyed reading this.
Please give your love and support to my Github open source repositories.
And If you are stuck somewhere or need to outsource your project feel free to hire us.

Thank You and Have Nice Day!