Create Dynamic Forms in Vue3.
Tags: vue, tutorial, forms
Published: 2021-01-12 14:46:11

New Year, first article! Let's get started. 🤩

So after several months of lockdown in Spain and really relaxing holidays, I worked hard on a plugin to bring **Dynamic Forms** to [Vue3](https://v3.vuejs.org/) and [Composition API](https://v3.vuejs.org/api/composition-api.html#composition-api) and finally, the stable 3.x version was released yesterday 🥳.

{% twitter 1348598628038934529 %}

But... what **Dynamic Forms** even mean? Well is basically a vue component that renders forms and inputs **dynamically** based on a data `object/schema` that represents the business logic.

```vue
<template>
  <dynamic-form :form="testForm" />
</template>

<script lang="ts">
...
setup() {
  const form = ref({
    id: 'test-form',
    fields: {
        username: TextField({
            label: 'Username',
        }),
        email: EmailField({
            label: 'email',
        }),
    }
  })

  return {
    form
  }
}
...
</script>
```

No more huge template files, no more "We need to do a new release because the client wants to change the username input label" 🤯. Forms schemas can be async and forms generated on go with an easy-going validation approach. All that for only **26kB**.

Still interested in **quickly create forms** in Vue 3.x? This article is for you.

![quickly](https://media.giphy.com/media/VZCFpF6sUyoG6l5SrY/giphy.gif)

## What we are going to build?

Just a simple login form similar to the one [here](https://vue-dynamic-forms.netlify.app/login). We are going to cover 
- Email input with validation, 
- Password input with validation
- Checkboxes
- Form submission

![Login Example](https://res.cloudinary.com/alvarosaburido/image/upload/v1610382808/blog/Create%20Dynamic%20Forms%20in%20Vue3/login-example_cm3omi.png)

## Create the demo app

For the app creation let's use [Vite](https://vitejs.dev/)⚡️

```bash
yarn create @vitejs/app my-login-demo --template vue-ts
```

## Installation

To install just run:

```bash
yarn add @asigloo/vue-dynamic-forms

# or, using NPM
npm install @asigloo/vue-dynamic-forms
```

The installation and usage have changed to align with the new Vue 3 initialization process.

To create a new plugin instance, use the `createDynamicForms` function.


```typescript
// main.ts

import { createApp } from 'vue';
import { createDynamicForms } from '@asigloo/vue-dynamic-forms';

const VueDynamicForms = createDynamicForms({
  // Global Options go here
});

export const app = createApp(App);

app.use(VueDynamicForms);
```

## Theming 

**Vue Dynamic Forms** is style agnostic, which means components have no predefined styles by default, so you can set them as you like. If you prefer a more `ready-to-go` solution for styling you can import a default `theme` file from the package like this and override the variables like this.

```scss
// styles.scss
$input-bg: #e2eb5d52;
$input-border-color: #aec64c;

@import '~@asigloo/vue-dynamic-forms/dist/themes/default.scss';
```

## Login Form

Go to your `App.vue` and add the `<dynamic-forms />` component into your template:

```vue
<template>
  <div class="app">
    <dynamic-form
      :form="form"
    />
  </div>
</template>
```

Next step is to write down the form schema using a `ref` in the `setup` method

You can also define `form` as a computed property in case using `vue-i18n` labels or to reactively assign a value to any form property such as visibility or dropdown options 


```typescript
import { defineComponent, ref } from 'vue';
import { EmailField, PasswordField, CheckboxField } from '@asigloo/vue-dynamic-forms';

export default defineComponent({
  name: 'App',
  setup() {
    const form = ref({
      id: 'login-form',
      fields: {
        email: EmailField({
          label: 'Email',
        }),
        password: PasswordField({
          label: 'Password',
          autocomplete: 'current-password',
        }),
        rememberMe: CheckboxField({
          label: 'Remember Me',
        }),
      },
    });
    return {
      form,
    };
  },
});
```

Let's open our browser and check our new form with the `vue-devtools`. If you don't have it install it yet and want to work with Vue3 support, I recommend you to install the Beta version on the chrome store [here](https://chrome.google.com/webstore/detail/vuejs-devtools/ljjemllljcmogpfapbkkighbhhppjdbg). It includes awesome new stuff such as a Timeline for component events.

![Vue dynamic forms dev-tools](https://res.cloudinary.com/alvarosaburido/image/upload/v1610446938/blog/Create%20Dynamic%20Forms%20in%20Vue3/Dynamic_Form_Login_dev_tools_i04rkq.png)

As you can see in the image above, each field was transformed into a `FormControl` object containing crucial info for its rendering and behavior. With this, you can easily check and debug your forms.

It took what? 5 minutes?. 🤭

![I have spoken](https://media.giphy.com/media/Rmx1KNhRJO4WQRPIFz/giphy.gif)

## Form submission

Now that we have our form in place, we want to do something with the input data. There are two ways to do it:

1. Use a `submit` button to trigger a `submit` event. (Recommended option, it also check if the form is valid).
2. Use the `change` event to get the latest state of the form values. (This one doesn't care about validation but it's helpful for autosave features for example)

### Using a `submit` button.

If you add a button of type `submit` just below the `<dynamic-form />` with the attribute `form` equal to the `form.id` it will trigger the form submission internally.

```vue
 <dynamic-form
   :form="form"
   @submitted="handleSubmit"
   @error="handleError"
 />
 <button
  class="btn"
  submit="true"
  :form="form?.id"
 >
  Sign In
 </button>
```

For this road, we have two (2) possible events:

1. `submitted` in case the validation went well and the form is `valid` (retrieve all values) ☑️
2. `error` in case there is an error in the form (retrieves all errors) ❌


### On change detection

The **DynamicForm** component also offers a `change` event in case you want to get the latest state of the form values right away. Is important to consider it will retrieve the values **without considering validation**, (errors will still be shown at UI level) so you probably want to do a second validation outside.

```vue
 <dynamic-form
   :form="form"
   @change="valuesChanged"
 />
```

```typescript
 setup() {
    const formValues = reactive({});
    const form = ref({
      id: 'login-form',
      fields: {
        // Form-fields
      },
    });

    function valuesChanged(values) {
      Object.assign(formValues, values);
      console.log('Values', values);
    }

    return {
      form,
      valuesChanged,
    };
  },
```


![Vue Dynamic Forms change detection](https://res.cloudinary.com/alvarosaburido/image/upload/v1610450225/blog/Create%20Dynamic%20Forms%20in%20Vue3/vue-dynamic-forms-value-changed_gewxwu.gif)

## Validation

Normally, forms submit data to a backend service, we want to make sure that the data required is sent, and is sent correctly so we don't end with errors in the console or "limbo" states in our application.

Let's make our **email** and **password** fields required for the submission. Just add a property `validations` with an array of all the validation you want the field to have, in this case, let's import the `required` validator like this:

```typescript
import { required, EmailField, Validator } from '@asigloo/vue-dynamic-forms`;
```

Then add it to your field definition:

```typescript
email: EmailField({
  label: 'Email',
  validations: [
     Validator({ validator: required, text: 'This field is required' }),
  ],
}),

```

If you try to submit the form empty, or you touch and blur the input without value it will add `error` classes to your component so you can style it accordingly 

![Vue Dynamic Forms validation error](https://res.cloudinary.com/alvarosaburido/image/upload/v1610452457/blog/Create%20Dynamic%20Forms%20in%20Vue3/validation-error_pv8yxz.gif)

If you correct the validation, which in this case, it's just adding a value to the field and you blur, a `success` class will be added to the control

![Vue Dynamic Forms validation success](https://res.cloudinary.com/alvarosaburido/image/upload/v1610452457/blog/Create%20Dynamic%20Forms%20in%20Vue3/validation-success_bbajtc.gif)

What about checking if the `email` format is correct and adding a complex validation to your password?

By default, **Vue Dynamic Forms** contains the following validations:

- required
- min
- max
- email
- URL
- minLength
- maxLength
- pattern.

So let's use the `email` and `pattern` validator to our cause:

```typescript
import {
  required,
  email,
  FormValidator,
  // ...
} from '@asigloo/vue-dynamic-forms';
```

```typescript
setup() {
    const emailValidator: FormValidator = {
      validator: email,
      text: 'Email format is incorrect',
    };
   // ...
   
   email: EmailField({
     label: 'Email',
     validations: [
       Validator({ validator: required, text: 'This field is required' }),
       emailValidator,
     ],
   }),
}
```

![Vue Dynamic Form email validation](https://res.cloudinary.com/alvarosaburido/image/upload/v1610454352/blog/Create%20Dynamic%20Forms%20in%20Vue3/vue-dynamic-forms-email-validation_yxqjmc.png)

Similar to this, let's use the `pattern` validation, this function is special because it takes an argument which is the **regex** pattern you want to apply to the validation.

```typescript
import {
  required,
  email,
  FormValidator,
  // ...
} from '@asigloo/vue-dynamic-forms';
```

```typescript
setup() {
    const passwordValidator: FormValidator = {
      validator: pattern(
        '^(?=.*[a-z])(?=.*[A-Z])(?=.*)(?=.*[#$^+=!*()@%&]).{8,10}$',
      ),
      text:
        'Password must contain at least 1 Uppercase, 1 Lowercase, 1 number, 1 special character and 
        min 8 characters max 10',
    };
   // ...
   
    password: PasswordField({
      label: 'Password',
      autocomplete: 'current-password',
      validations: [
         Validator({ validator: required, text: 'This field is required' }),
         passwordValidator,
      ],
    }),
}
```

![Vue Dynamic Form password validation](https://res.cloudinary.com/alvarosaburido/image/upload/v1610454352/blog/Create%20Dynamic%20Forms%20in%20Vue3/vue-dynamic-forms-password-validaiton_qfvmh9.png)


## Wrap Up

So that's pretty much it, you can check the complete solution [here](https://github.com/asigloo/vue-dynamic-forms/blob/master/demos/vue-3/src/views/Login.vue) (it also shows how to use with [TailwindCSS](https://tailwindcss.com/))

Of course, this is a pretty simple example, but I will post more use cases in the near future, such as **async form data**,**i18n**, **custom fields**, and **third-party components**

If you have any questions feel free to open a discussion on the comment section or ping me on Twitter [@alvarosaburido](https://twitter.com/alvarosaburido1). I'm always hanging around.

We're also looking for **contributors** to improve and maintain the [repo](https://github.com/asigloo/vue-dynamic-forms), If you are interested in the challenge please DM me.

![This is the way](https://media.giphy.com/media/sEJ2j80ZmgXPOlEYEt/giphy.gif)