Form Component Pattern — Transcript

Transcript of the free Vue.js lesson Form Component Patternwatch the video lesson.

In this lesson, let's take a look at the form component pattern. This pattern allows us to use vModel on an entire form, just like we normally would on an individual input. Not only that, it comes with a few other goodies. For example, it allows us to update the values within the form without having that value update in the parent until we actually commit them with some kind of submit button.

This makes a lot of sense and is the workflow that users typically expect. Plus, using this approach also takes advantage of native form validation. This name input, for example, has the required attribute on it. And if I try to submit the form without a name, I get the native validation on the page.

As soon as I fix it, hit submit, the value is updated in the parent. Same thing goes for email fields. If I don't have a valid email address in here, the native validation kicks in and the email is not updated in the parent. This is a super slim yet effective way to do forms that takes advantage of browser native APIs without a lot of work on our part.

A valid email address again, of course, does go through and updates the parent. Okay, so how does this work? view and go through what I've done here. The first thing I've done is called the define model function that's built into view.

This is what allows us to capture what the consuming component, the parent component, passes in for vModel. So in this case, this user object right here is the initial value of model value. Then what I do is I clone the model value and set it to a local piece of state, a local ref called form. Now it's important when you're passing in objects as props, or in this case as the V model, that you clone the object before using it within your child component.

If you're doing nothing more than displaying that data to the page, then it's not a big deal, you don't have to clone. But for any other use case, and 99% of the time, if you're passing in an object, you want to clone it before you use it. Why? Because objects are passed by reference and not by value.

Meaning that if we did not clone the model value here before assigning it to form, then any time we updated the value of form, the value of model value would change as well, completely nullifying the approach of copying model value and setting form to it. So, make sure to clone those object props. The easiest way I've found to clone objects in JavaScript is simply JSON stringifying them and then JSON parsing them again. There is a browser native function now.

called structured clone. It is supposed to do something similar and in a better way, but I've had problems with it in the past, so I typically tend to stick with this approach. Okay, so we're getting the model value in from the parent. We're cloning that model value and sending some local data to that cloned object.

If that model value isn't provided, then we're just defaulting to an object with name, email, and age. Great. Here is the key though. Now when I'm binding data to my inputs in the template below, I bind them to our local data instead of the model value.

If I were to bind it to the model value, then we would see updates in the parent data as soon as we type in one of the individual fields, thus losing the ability to commit all the data at one time with the submit button and also losing the benefit of that built-in native field validation. So we'll change this back to form and then let's take a look at the handle submit function. This is what runs whenever we hit the submit button. All handle submit does is set the model value to a clone of our local value.

which is exactly why when we hit submit, the data updates in the parent. Because whenever we alter this model value, under the hood view is emitting a special event called update model value, which is listened for by vModel in the parent to update the user inside of the parent. Under the hood, all define model really is, is a special prop called model value. that emits a special event called update model value whenever model value, the return of defined model, is altered.

So there you have it. That is how the form component pattern works. But before we close the video, I wanted to show you one other cool little trick that you could do with it. Let's say you want this form to be useful for both creating new users and editing existing ones.

Well, let's say that we just set the user value here to null, meaning that the user doesn't exist yet. Over inside of the user form component, that means we're now defaulting to this blank user object. Down inside of my button, I have some logic, not on the form, the local data. because it's always going to be equal to some user object, whether it's a user object that's cloned from the model value, or it's this default user object.

So we're not checking that. Instead, we'll check model value. e. the user object exists, then we are editing the user.

If the model value does not exist, then we are creating a new user. That makes this form useful then for both creating and editing, and I can have my submit button automatically update based on whichever case it is. Take a look at this in the browser, give the page a refresh. So when the user is initially null, we have null values for each of these.

It says create user. If I type in Daniel, hit create user, then the label changes to edit user. Notice that I didn't have the required attribute on the email or the age, which is why it allowed my form to submit. That did confuse me for just a moment.

But if I were to provide an invalid email here and hit edit user, then I would get the validation error. If I go back over to my code and add required to both of the other inputs as well, and then tried to submit a form with only my name, it would not submit the form and the data would never make it to the parent. Only when I fill it out completely this time and hit the Create User button does it update in the parent. Finally, let's handle one last edge case I forgot to mention a moment ago with this component pattern.

If I open up the Nuxt DevTools with Shift-Command-D, these are a lot like the regular View DevTools, They're just a little bit more specific for the next context, but you should already be familiar with this panel here. We're able to select a component and then see the data that's defined in that component. So this is the page, the parent, and here we have the updated user because we have submitted the form. However, if I were to increment the user's age to eight and close the DevTools, Notice that we have age eight displayed in the parent, but down inside of the form, it's still at seven.

This is not quite how we expect vModel to work. We expect vModel to always be in sync, meaning when I change the value in the parent, it then flows down to the child. Well, under the hood, model value actually has changed in the child. but it's our form that has not, and it's the form that's being displayed in the fields.

So what we need to do is we need to watch model value, and then whenever model value changes, we'll reset the value of form to the updated model value. Lastly, we will watch model value deeply since it is an object. Back over in the browser now, you can see that it is eight. That is the hot module reloading in action.

Let me open the DevTools one more time, increment it to nine just to be sure. And perfect, we are good to go. In this lesson, let's take a look at the form component pattern. This pattern allows us to use vModel on an entire form, just like we normally would on an individual input.

Not only that, it comes with a few other goodies. For example, it allows us to update the values within the form without having that value update in the parent until we actually commit them with some kind of submit button. This makes a lot of sense and is the workflow that users typically expect. Plus, using this approach also takes advantage of native form validation.

This name input, for example, has the required attribute on it. And if I try to submit the form without a name, I get the native validation on the page. As soon as I fix it, hit submit, the value is updated in the parent. Same thing goes for email fields.

If I don't have a valid email address in here, the native validation kicks in and the email is not updated in the parent. This is a super slim yet effective way to do forms that takes advantage of browser native APIs without a lot of work on our part. A valid email address again, of course, does go through and updates the parent. Okay, so how does this work?

view and go through what I've done here. The first thing I've done is called the define model function that's built into view. This is what allows us to capture what the consuming component, the parent component, passes in for vModel. So in this case, this user object right here is the initial value of model value.

Then what I do is I clone the model value and set it to a local piece of state, a local ref called form. Now it's important when you're passing in objects as props, or in this case as the V model, that you clone the object before using it within your child component. If you're doing nothing more than displaying that data to the page, then it's not a big deal, you don't have to clone. But for any other use case, and 99% of the time, if you're passing in an object, you want to clone it before you use it.

Why? Because objects are passed by reference and not by value. Meaning that if we did not clone the model value here before assigning it to form, then any time we updated the value of form, the value of model value would change as well, completely nullifying the approach of copying model value and setting form to it. So, make sure to clone those object props.

The easiest way I've found to clone objects in JavaScript is simply JSON stringifying them and then JSON parsing them again. There is a browser native function now. called structured clone. It is supposed to do something similar and in a better way, but I've had problems with it in the past, so I typically tend to stick with this approach.

Okay, so we're getting the model value in from the parent. We're cloning that model value and sending some local data to that cloned object. If that model value isn't provided, then we're just defaulting to an object with name, email, and age. Great.

Here is the key though. Now when I'm binding data to my inputs in the template below, I bind them to our local data instead of the model value. If I were to bind it to the model value, then we would see updates in the parent data as soon as we type in one of the individual fields, thus losing the ability to commit all the data at one time with the submit button and also losing the benefit of that built-in native field validation. So we'll change this back to form and then let's take a look at the handle submit function.

This is what runs whenever we hit the submit button. All handle submit does is set the model value to a clone of our local value. which is exactly why when we hit submit, the data updates in the parent. Because whenever we alter this model value, under the hood view is emitting a special event called update model value, which is listened for by vModel in the parent to update the user inside of the parent.

Under the hood, all define model really is, is a special prop called model value. that emits a special event called update model value whenever model value, the return of defined model, is altered. So there you have it. That is how the form component pattern works.

But before we close the video, I wanted to show you one other cool little trick that you could do with it. Let's say you want this form to be useful for both creating new users and editing existing ones. Well, let's say that we just set the user value here to null, meaning that the user doesn't exist yet. Over inside of the user form component, that means we're now defaulting to this blank user object.

Down inside of my button, I have some logic, not on the form, the local data. because it's always going to be equal to some user object, whether it's a user object that's cloned from the model value, or it's this default user object. So we're not checking that. Instead, we'll check model value.

e. the user object exists, then we are editing the user. If the model value does not exist, then we are creating a new user. That makes this form useful then for both creating and editing, and I can have my submit button automatically update based on whichever case it is.

Take a look at this in the browser, give the page a refresh. So when the user is initially null, we have null values for each of these. It says create user. If I type in Daniel, hit create user, then the label changes to edit user.

Notice that I didn't have the required attribute on the email or the age, which is why it allowed my form to submit. That did confuse me for just a moment. But if I were to provide an invalid email here and hit edit user, then I would get the validation error. If I go back over to my code and add required to both of the other inputs as well, and then tried to submit a form with only my name, it would not submit the form and the data would never make it to the parent.

Only when I fill it out completely this time and hit the Create User button does it update in the parent. Finally, let's handle one last edge case I forgot to mention a moment ago with this component pattern. If I open up the Nuxt DevTools with Shift-Command-D, these are a lot like the regular View DevTools, They're just a little bit more specific for the next context, but you should already be familiar with this panel here. We're able to select a component and then see the data that's defined in that component.

So this is the page, the parent, and here we have the updated user because we have submitted the form. However, if I were to increment the user's age to eight and close the DevTools, Notice that we have age eight displayed in the parent, but down inside of the form, it's still at seven. This is not quite how we expect vModel to work. We expect vModel to always be in sync, meaning when I change the value in the parent, it then flows down to the child.

Well, under the hood, model value actually has changed in the child. but it's our form that has not, and it's the form that's being displayed in the fields. So what we need to do is we need to watch model value, and then whenever model value changes, we'll reset the value of form to the updated model value. Lastly, we will watch model value deeply since it is an object.

Back over in the browser now, you can see that it is eight. That is the hot module reloading in action. Let me open the DevTools one more time, increment it to nine just to be sure. And perfect, we are good to go.