DEV Community

alireza valizade
alireza valizade

Posted on • Originally published at Medium on

How to deal with forms in Vue (Create your own flexible form library) | Chapter 1

In this article I’ll show you how much enjoyable is working with forms by building a super flexible library.

Spoiler

Here is min , Which I have been created And would like to share my experiences with you guys.

It’s been years which I worked with complicated forms, I used cool libraries like FinalForm or Formik and in the past tools like Redux-Form, But recently I decided to make some challenges for my self and create a library for building forms in Vue which I’d like share my experience here because a lot of developers hates from building forms but I’m going to show you it is not really hard and I like it!

Chapters

  1. Building the form library (We’ll build something basic then we’ll improve it in further steps)

  2. Connecting the form components to third party libraries

  3. Connecting the form components to native html inputs

Topics

What we’ll build are just two components Form and Field, Form is the parent and the Fields will be the children of it and then the rest is just about sharing the “State” between them, from parent to children (and Field for custom component and third party libraries) and also something important which is “Reactivity” in Vue.

Let’s take a look at what we’re going to build and some features:

  • Form and Field state (complete report for each Field and the Form)
  • State management
  • Custom validations (Write your own validation, you just need to return true or false and the error message)
  • Handling Validations (Form level and Field level, You can pass your validation to Form or Field)
  • Custom Props (like running validation on blur or just focus or …)
  • Initializing the Form and listen to changes of it
  • Custom Hooks (like “@create(form)”, form example you can use the form methods in your current component)
  • Make it subscription based (This could be useful when you need optimisation for your app, Imagine you have 10.000 thousand of dynamic fields in one component)
  • Field Arrays and with simple methods like push(), remove(), swipe()
  • And more….

Let’s install a few helpers from Lodash which makes our life easier.

yarn add lodash.set lodash.get lodash.isequal

Understanding the Form and Fields state properties

Each Form and Field has state which includes some properties which have special meaning and they happened in the special time which are accessible from outside or inside of the component (You’ll get the point later). Here is a translation of them, Let’s take a look at the states properties:

FormState:

FieldState:

State management

The basic Form.vue will be like this:

Yeah, We are going to use all the Vue’s power 😅. We need all of it.

We added slot to be able render the children there, We also can bind the FormState to get the state of form directly in children.

Let’s start with fields states in form state. We need to add a property in our data and call it fields like this:

data() {
 return {
 fields: {},
 };
},

This is main part of controlling the fields, Basically we are building controlled components which are our fields.

Next step, We need something that we could manipulate the state from outside and inside the Form component. so we need a method to register a Field (add fields property in data) and then another one for unregistering it. For registering a field we need the exact field state which we talked above, So we need initial state for each field.

registerField() : For registering a new field. Each field has a name, Even for handling nested properties we just need to keep them like this. We can use get to compute them and return them in values , For example:

These two fields will be register like this:

This’ll make our life very easier, We won’t care about the nested fields and the Reactivity for nested properties, But in other hand we have the all the benefits of them, we can compute them and return them in a main object with nested properties using the set method from Lodash.

unregisterField() : For removing an exist field and setting it to undefined is enough because they are reactive (after registering them).

initFieldState() : Initial field state, which we’ll make them subscription based using props later for performance reasons. we’ll listen to user actions and calculate them based on it.

OK, These are fine but as I said we need to manipulate the state from outside and also inside the Form component, How can we access to these methods in everywhere in children (regardless of how deep they are)? We’ll use Provide and Inject here. It’ll save our life from repeating ourselves. Take a look at my previous article about the Context in Vue.

These are enough just for updating the state but not at all we need, one more thing here is required which is need to listen the changes and get the current state of the each field here. I prefer method instead of computed property because I want to get the state based on a parameter which is name of the field, Something like this:

And also provide it to children:

Next step, We need a method to change the value of the field directly, We need a name to find the field, and second parameter will be the value to update the field state. Also we need to provide it to children.

// methods
change(name, value) {
 this.fields[name].value = value;
},
// provide
{
 //...
 change: this.change
}

Let’s see how will look like our Field component, But for now I’ll just handle it for custom components because natives input has more stuff todo which we’ll handle it in further steps.

As you see we injected provided methods from Form, In created we’re calling registerField , In destroyed we’re calling unregisterField , That’s what we need to add or remove the fields, We have fieldState computed property which is returning result of getFieldState method by passing name from props. Also we used slot to render dynamic and custom children (components). I’ve created some more computed properties which will make our life easier.

Custom Validations

Let’s make this flexible, What we need is just passing Pure Functions to Field or Form and pass the value of the field and return error messages based on your own conditions. It could be Array of functions (in this case we need to compose them to return first error) or single function.

Handling Validations

What we need is create a wrapper function around the validations and run it on each change. Let’s define validate prop in both components.

// Form
props: {
 ...
 validate: {
 type: Function,
 default: () =\> (null)
 }
}
// Field
props: {
 ...
 validate: {
 type: [Function, Array],
 default: () =\> (null)
 }
}

Form validation

We need to call it on every change in form.

Field Validation

We need to pass the validate function to field state when we are registering the field on created hook then we can access to it and run them from field state like this,

We need to call it in change(name) function.

The values is a computed property which returns all the values in field by calling getFieldState(name) .

Custom Props

As you know this architecture is flexible you can do whatever you want, I want to show you some of this features like validateOnBlur .

Let’s define a prop in Form and call it validateOnBlur in form, We want to validate the fields only when blur called instead of change() , The only thing you need is to create blur and focus function and change the field state. Also you need to inject from Field component and pass it to your fields.

I created a helper and called it setIn(name, propertyName, value) which change the field state directly and then calling the validation functions.

Initializing the Form

As I mentioned every field even nested has only one key (like “user.firstname and user.lastname”) with this trick we can easy handle nested fields.

Let’s define a prop and call it initialValues and initialize() method in the Form. We also want to re-initialize the Form on changing the initialValues , So let’s also use watcher to listen to changes.

Custom Hooks

Sometimes we need to access to form object in the current component which we defined the form, like here:

As you see, I want to be able to access to form object from FormState to do some stuff which I commented in screenshot. This is the moment we need to define custom hooks for our Form.

Then we could easily use it like this:

Subscription based

As you know, We have a lot computed properties which are related to reporting the FormState and FieldState which could be a killer for us when talking to performance. Because they have a lot of logic inside themselves which need to be calculated, Also they’re listening to each change to be called.

Let’s define a prop in Form or Field and call it subscriptionItems, Then we could attach dynamic compute properties beforeCreated life cycle hook in Vue,

We could do the same thing for Field.

Field Arrays

Again, I should to notice that, Our fields are just simple strings with valid identity in Javascript for defining properties in objects.

The values computed property is handling with set() from Lodash. So you can guess what’s the output of it. But this is not enough for having dynamic fields or some kind of a repeater which can control by user and also programmer.

For handling this you could easily create same thing with Field component but having more methods like map, push, remove then pass it to fieldState() , Responsibly of map is just returning array of names and then push is for adding new row or object to our value in fieldState and remove for removing an item from fieldState .

The you can use it like this:

Result:

conclusion 🎉

Yeah, We made it. Forms always are something interesting and I like to work with them and help others to create what they want. Thanks for reading it.

Here is mine, What I have been build. You can go over the code and understand it easily, I’m open to help you 👷‍♂️ , If you have question open an issue on Github or Respond here.

Live demo

https://codesandbox.io/s/simple-form-using-vue-controlled-form-fields-z2twq?fontsize=14&hidenavigation=1&theme=dark

FOR MORE EXAMPLES CLICK ON THIS LINK

Top comments (0)