In this blog series, I'm going to tell how to create reusable form components in Vue. Let’s start with the simplest one – Text Field.
This post assumes you already know the basics of Vue components.
First, we have to determine our needs. Every form component has to:
- Act like a native HTML element
- Use two-way data binding
- Handle backend validation errors
So let's start with defining a wrapper for a simple input component. Create a file called "TextField.vue".
There’s definitely no magic at this step. We just made a simple wrapper for a native input element. Now we can use it in any other component the same way we usually do with native elements.
Create a file “Playground.vue” (or whatever) and import our component.
As you may have noticed, the component already lets us use two-way data binding via v-model, and also accept any attributes applicable to a native input (i.e. “type”, “class”, “required”, “disabled”, “max” and “min” etc.). You can play around with different attributes.
Now it’s time to add a label to our input. Let’s get back to the component.
Don’t forget that the Vue component must have a single root component. In the example above I just wrapped everything in a DIV.
Look at this more in detail. Vue automatically binds all applied attributes to a root element. Since the input is not a root anymore, we have to disable this ability by setting “inheritAttrs” to false, and bind attributes to the element manually by adding v-bind=”$attrs”. You can still apply any attributes to the component and it will just work.
Now we can pass a label to a component as a property.
So we just made a component that acts like a native input, uses two-way data binding, and also has a separate label.
Our third requirement is to handle validation errors. I’m not a fan of custom frontend validation since I truly believe that there’s no need in such complexity when builtin Laravel validator does its work great.
Assume that we are making an AJAX request with the wrong data that won’t pass the validation.
Laravel validator returns data in the following format:
We need to store the validation messages to the “errors” object and pass the appropriate child to our component.
Then the component has to determine if there’s at least a single error and show a corresponding message.
Now our component shows the list with error messages if any presented, and also applies the class “is-invalid” to the input element.
Wrap it up
We just ended up building our first reusable component. But since we are making a set of form components, we can move out the repeating part of the code to a separate component.
So let’s make a universal field wrapper that will be handling validation errors and showing any kind of additional information (in our case — a label). Make a file “DefaultField.vue”.
Then make some changes to our TextField and wrap it into a new DefaultField component.
As you may have noticed, we can’t just access the properties of a “parent” component, instead, we may pass any attributes to the slot (DefaultField.vue) and access them from the wrapped component via slot props (TextField.vue).As you may have noticed, we can’t just access the properties of a “parent” component, instead, we may pass any attributes to the slot (DefaultField.vue) and access them from the wrapped component via slot props (TextField.vue).
You just built your first “reusable” form component. Feel free to hit me up on Twitter (and don’t forget to follow) if you have any questions or improvements regarding the tutorial. In the next chapter, I'll tell how to make similar wrappers around a few popular external libraries such as flatpickr and v-select to make a consistent component set.