Validating reusable forms with Vuelidate
Tags: vuelidate, form, reusable, validate
Published: 2019-10-08 23:13:07

I'm Iago Rodrigues, frontend developer. I'm from Brazil and I work with Vuejs for more than 2 years as a Freelance. I love coffee and that might be making me crazy, alongside with the the code problems that I find in the way. Like this pretty one here.

For a long time, I've had an issue that was making me crazy. I needed to reuse a form in some views and I needed to validate those forms as well. Now, in the world of modularization, `ctrl + c | ctrl+ v` a component is going to give you a headache and probably your last paycheck. Let's see how we can solve this issue and, hopefully, keep our belly full of beer :beer:!


## The problem

Usually, when we create a [CRUD](https://pt.wikipedia.org/wiki/CRUD), we need to use the same form in the Create and Show|Edit view. And, in the top of that, we need to validate the fields of the form. 

The faster solution for this, is to copy all of the form component (or view, depends on where you will implement it). However, this will create a scar on the best practices of coding, specially [DRY](https://www.codementor.io/joshuaaroke/dry-code-vs-wet-code-89xjwv11w) which is one of the pillars of [Vue](https://vuejs.org/). 

In this article, you will find a better solution for this, but first, we need to prepare our code to get the tools that will help us do that.


## The preparation

First, we need to create a project with Vue.

```sh
  npm i -g @vue/cli

  vue create validating-reusable-forms
```

Choose the following features for the project:

![Choosing project's features](https://thepracticaldev.s3.amazonaws.com/i/kl0i1v89gbj6hyflxp60.jpg)

If you wanna follow this tutorial thourough, you can clone this repo
{% github oiagorodrigues/validating-reused-forms no-readme %}

After the installation is complete, add `BootstrapVue` (or you can use any css you desire) and `Vuelidate` to the project. 

> Tip: when you install a plugin in you project, use [YARN](https://yarnpkg.com/lang/en/) for that as it's faster and more reliable. For global installation, use `npm`.

```sh
  yarn add vuelidate bootstrap-vue
```

```js
// main.js

import Vue from 'vue'
import router from './router'
import store from './store'

import BootstrapVue from 'bootstrap-vue'
import Vuelidate from 'vuelidate'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

import App from './App.vue'

Vue.use(BootstrapVue)
Vue.use(Vuelidate)

[...]

```

With all the setup done, we can now make it work.

## The solution

Create a file named `Forms.vue`. It can be located in the `components` folder, but I recommend to create a folder, inside `Views`, and give the name of your module to it.

In the `Form.vue` component, define the structure of the form:

```js
// Form.vue

<template>
  <b-form-row class="justify-content-center w-50 mx-auto">
    <b-col cols="12">
      <b-form-group label="Name" label-for="name">
        <b-form-input
          v-model="$v.model.name.$model"
          id="name"
          :disabled="disabled"
          :state="$v.model.name.$dirty ? !$v.model.name.$error : null"
        ></b-form-input>
      </b-form-group>
    </b-col>

    <b-col cols="12">
      <b-form-group label="Age" label-for="age">
        <b-form-input
          v-model="$v.model.age.$model"
          id="age"
          :disabled="disabled"
          :state="$v.model.age.$dirty ? !$v.model.age.$error : null"
        ></b-form-input>
      </b-form-group>
    </b-col>

    <b-col cols="12">
      <b-form-group label="Sexual Orientation" label-for="sexualOrientation">
        <b-form-select
          v-model="$v.model.sexualOrientation.$model"
          id="sexualOrientation"
          :disabled="disabled"
          :options="sexualOptions"
          :state="$v.model.sexualOrientation.$dirty ? !$v.model.sexualOrientation.$error : null"
        ></b-form-select>
      </b-form-group>
    </b-col>

    <b-col cols="12">
      <b-form-group label="Like music?">
        <b-form-radio
          v-model="$v.model.likeMusic.$model"
          name="like-music"
          value="true"
          :disabled="disabled"
          :state="$v.model.likeMusic.$dirty ? !$v.model.likeMusic.$error : null"
        >Yes</b-form-radio>
        <b-form-radio
          v-model="$v.model.likeMusic.$model"
          name="like-music"
          value="false"
          :disabled="disabled"
          :state="$v.model.likeMusic.$dirty ? !$v.model.likeMusic.$error : null"
        >No</b-form-radio>
      </b-form-group>
    </b-col>

    <b-col cols="12">
      <b-form-group label="Describe yourself" label-for="desc">
        <b-form-textarea
          v-model="$v.model.desc.$model"
          id="desc"
          placeholder="Enter something..."
          rows="3"
          max-rows="6"
          :disabled="disabled"
          :state="$v.model.desc.$dirty ? !$v.model.desc.$error : null"
        ></b-form-textarea>
      </b-form-group>
    </b-col>

    <b-col cols="12">
      <b-form-checkbox
        v-model="$v.model.termsOfUse.$model"
        id="termsOfUse"
        name="termsOfUse"
        value="accepted"
        unchecked-value="not_accepted"
        :disabled="disabled"
        :state="$v.model.termsOfUse.$dirty ? !$v.model.termsOfUse.$error : null"
      >I accept the terms and use</b-form-checkbox>
    </b-col>

    <slot class="mt-3" name="action" />

    <b-col class="mt-3" cols="12" sm="4" md="3" lg="2">
      <b-btn variant="outline-danger" block @click="handleReset">Reset</b-btn>
    </b-col>
  </b-form-row>
</template>
```

Then, import the desired vuelidate rules and create its backbone.

```js
// Form.vue

<script>
import { required } from 'vuelidate/lib/validators'
export default {
  name: 'Form',
  validations: {
    model: {
      name: { required },
      age: { required },
      sexualOrientation: { required },
      desc: { required },
      likeMusic: { required },
      termsOfUse: { required }
    }
  }
}
</script>
```

In order to make the form reusable, we do not need to generate its data. We will receive it by props.

```js
// Form.vue

<script>
[...]
props: {
    form: {
      type: Object,
      required: true
    }
},
[...]
</script>
```

Then we need to copy the form prop into our data property, so that we don’t override the data coming from the parent.

```js
// Form.vue

[...]
data () {
  return {
    [...]
    model: { ...this.form },
  }
}
```

Lastly, in order to trigger Vuelidate’s methods and allow the parent to submit the form with the changed data, we need to define some methods:

```js
// Form.vue

[...]
methods: {
   [...]
    submit() {
      this.$v.$touch()
      return new Promise((resolve, reject) => {
        if (this.$v.$invalid) {
          reject('Error: Invalid Form!')
        }
        return resolve({ ...this.model })
      })
    },
    handleReset() {
      this.$v.$reset()
      this.$emit('reset')
    }
  }
```

There are some ways for a Child to communicate with its Parent component. One of then (and probably the cleaner one) is to use a promise. We return one resolved or rejected, so the parent can access the form's data without the need to use `$children` or listen to an event. We reject a string only to return an error, but you don't need to show this message.

If you notice the form's template, we are not calling the `submit` method anywhere. That's because the parent will call this method when it trigger a submit action.

You might be wondering why the template doesn't have a submit button as well. In order to make this form reusable, we also need to remove any business logic from it. That's why we have only a `reset` button and a `action` slot. The parent will implement a button which will do the business logic of its View.

Speaking of `reset`, in the form we are only triggering the `$v.$reset`, so that we clear vuelidate's object and rules, and then we are emitting an event, in order to the parent to do something else.

### Creating Views

Let's consume the form creating two views. `New.vue` and `Show.vue`. The first one will be handling the creation of the data (being a Person, user, book, etc...) and the last will handle the visualization and edition of a single data.

```js
// NewPerson.vue

<template>
  <b-container>
    <Form ref="form" :key="`form_${formId}`" :form="form" @submit.prevent>
      <template v-slot:action>
        <b-col class="mt-3" cols="12" sm="4" md="3" lg="2">
          <b-btn variant="primary" block @click="handleSubmit">Submit</b-btn>
        </b-col>
      </template>
    </Form>
  </b-container>
</template>
```

We instantiate the Form component passing a `ref` to it and call the `action` slot to define a button to do our business logic.

The method will be like this:

```js
// NewPerson.vue

[...]
handleSubmit() {
    this.$refs.form
      .submit()
      .then(payload => {
        // ... do the logic of the component here
      })
      .catch(error => console.error(error))
  },
```

You can do the same in the `Show.vue` view. You just need to change the button to something like this:


```js
<template v-slot:action>
   <b-col v-show="disabled" class="mt-3" cols="12" sm="4" md="3" lg="2">
      <b-btn variant="info" block @click="disabled = false">Edit</b-btn>
   </b-col>
   <b-col v-show="!disabled" class="mt-3" cols="12" sm="4" md="3" lg="2">
       <b-btn variant="primary" block @click="handleUpdate">Update</b-btn>
   </b-col>
</template>
```

And then you can receive this disabled attribute as a props in the Form component.


## A little bit more

You might have noticed that we repeated some code in the `New.vue` and `Show.vue` views. That is kind of true. We did repeated a few lines in order to implement the same component, but the majority of the implementation is different. That being said, I don't think it'll hurt the DRY practice.

But, to clear your mind, you can create a `Partials` folder, inside the module, and put a component with the code that we repeated and you can make it work as a wrapper, calling it in the views and just implementing the different parts.

However, try to don't do the logic in the partials component.

## Take a look

{% codesandbox validating-reusabled-forms-e232y %} 


## Wrapping up

This way, we can define the backbones of a form and use it anywhere that we need a similar form. And the parent component doens’t need to worry about validations. That’s all handled in the Form component.

We can deliver reusable, maintanable and clean code following these guidelines.


## Find me

* [Linkedin](https://twitter.com/iagokv)
* [Twitter](https://www.linkedin.com/in/iago-rodrigues/)

{% user oiagorodrigues %}

[Image credits](https://pageclip.co/blog/2018-02-20-you-should-use-html5-form-validation.html)