React Hook Form - Complete Tutorial (with Zod)

143.17k views5718 WordsCopy TextShare
Cosden Solutions
🚀 Project React → https://cosden.solutions/project-react 📥 Import React (Newsletter) → https://co...
Video Transcript:
React gets a lot of things right. The solutions  that it provides and the paradigm shifts that it offers us when building web applications  work really well. But, unfortunately, nothing is perfect.
There are some things in  React that if you try to do them natively, without any sort of third-party library, you're  not going to have such a good experience. One of such things is forms. If you try to build a form  natively in React, oftentimes you end up spending more time trying to prevent the default behavior  rather than encouraging it.
So, in this video, I'm going to show you an alternative to building  native forms in React. It's called React Hook Form and it's a form library that makes it really easy  for you to build complex and performant forms in React. Let's get into it.
All right, let's begin.  So, first, I want to talk about and show you why you would want to use a form library like React  Hook Form in the first place. So, here what I have is I have a component that renders out a  very simple form.
It just has an email and a password field. This is the JSX that is rendering  this form. You have the HTML form right here, you have the HandleSubmit function, and then you have  your two inputs, one for the email and one for the password.
The code that's running this form is  actually quite simple. You have two states here, one for the email, one for the password. These are  getting sent to the actual inputs to update those values.
Then you have a state here for the errors.  This is a simple object with just the email and the password as properties. And then you have  the HandleSubmit function, which will take the actual form event, will first prevent the default,  then will set the errors to null because this is a new form submission, and then will perform some  very basic, simple manual validation, like just checking that the email has an at symbol.
And  then if we get here to this part of the code, we consider the form validated, and then we  console log form submitted. This is what the form looks like. You have your email and password  field.
If I press submit, we get here this error that basically shows that the email must include  an at symbol. And then if I just add the at symbol and I put something here, and I submit it, we  actually get a log in our console that says the form has been submitted. This form works. 
Now, if you look at this code, just like this, you might ask yourself, why would we even need a  form library? I mean, this works just fine. And you're right, it does.
But the thing is, this is  a very simple form. This only has two properties, the email and the password. And for simple  forms like this, I actually wouldn't even recommend that you use something like React hook  form, because it's overkill.
For a simple form like this, doing it this way is completely fine.  The problem, however, is the fact that this form doesn't scale well at all. This is a very simple  form.
And for simple forms, this works. But as soon as you want to add any sort of complexity  to it, even just adding more fields here, this starts to get very complicated very quickly.  For every new field that you want to add, you have to basically add a new state property.
You have to  manage that field. You have to manage its updates. You have to then pass it to the actual input. 
So you would have to add another input as well, add another onChange property here and the value  here. And if you have a form with like 10 inputs, you can quickly see how much code you're going  to have to write just to get that working. Also, any new field that you add, you have to also  remember to put it here in this arrows object, which also means that you have to put it here  to make sure that it gets reset properly.
And then you have to handle your own validation. Even  worse, let's say that instead of console logging that the form is submitted, you actually want to  take its data, send it to a server and await the service response, you would want to turn this into  an asynchronous function. Well, if you did that, you would also probably want to show something  to the user as this form is being submitted to display some UI that something is happening.
You  might also want to prevent this button from being clicked while a form is submitting to prevent a  form from being submitted multiple times. To do that, you would have to come here and create  your own loading state. You would then have to make sure that you're properly handling the  loading state inside of this function here.
So that means setting it to true before the form  submits and then setting it to false after the form has submitted and got a response back from  the back end. And you can quickly see that this is becoming very complicated and you have to write  a lot of code just to get a form to work. And that is why you would want to use a form library like  React hook form.
React hook form allows you to do all of this and more with a lot less code and a  much better development experience. So now let's actually get rid of all of this code and let's  reimplement the same form using React hook form. So first I'm going to delete all of this code  because I'm not going to need it.
Then in the form here, I want to get rid of this on submit  function. I want to also get rid of the value and the on change on the inputs. And I'm also  going to get rid of these errors here.
So let me do the same for password, get rid of this, and  then we can get rid of this imports here because we no longer need them. Now we just have a simple  basic form without any functionality. To create a form with React hook form, we're going to make use  of the use form custom hook.
So we'll come here at the top and we'll import use form from React hook  form like so. And then inside of the component, we're going to come here and we're going to write  const form equals use form like so. This is the most basic version of use form.
Now if you're  using TypeScript, there's one more thing that we have to do. If you're not using TypeScript,  it's totally fine. You can just skip this very short part.
We're going to define here the types  of our actual form fields. So we're going to do type form fields, and that is going to be equal  to email string and let copilot complete the rest. We're also going to have password string. 
So these are going to be the actual form fields that we're going to use within our form. And  then we're going to take this form fields and we're going to give it as the type of use form  like this. We're going to do form fields.
And so this will make it so that whenever we work  with anything form related in React hook form, we're always going to have this properly typed.  The next step for us to do is to actually connect this form element here and these inputs here to  React hook form. Because just by writing this code, it's not enough.
We actually have to connect  all of these inputs and HTML elements for them to be able to work with the form. And the way that we  do that is we're going to make use of the register function that we get by accessing use form. So  I can actually destructure this form variable here and I can do register this function.
I can  use it on an actual input and then register that input with React hook form. So I'm going to come  here and start by registering the email input. So I'll come here.
I'll do curly bracket dot dot dot  register and then call this and then give it here the actual field. And because we have properly  typed these fields, we get here auto completion, email or password. I'm going to select password. 
And now this input field has been registered to the form on this email field right here. Let's now  do the same thing to the password input. So we'll come here, we'll do dot dot dot register.
And  this one will give password. With this, both of our inputs fields are registered to the form. So  this means that now whenever they change values, whenever we type in these input fields, that  value is going to get sent to React hook form, and it is going to manage everything for us  instead of us having to do it ourselves.
Next, we actually need to also connect this form element  here so that on submit, we actually call our own custom on submit function instead of submitting  it to the default behavior, which is to send it to an actual URL. So first, what we're going  to do is we're going to define our own custom on submit function. So we're going to do const on  submit.
And now if you are working in TypeScript, you're going to want to type this as submit  handler, which you can import to directly from React hook form, and then give it form fields.  If you're in JavaScript, just don't do this, it's totally fine, then it's going to be equals  data. And then we're just going to for now, console dot log data, and then close this out  right here, typing this function as submit handler, and then form fields is actually going  to make this data here be properly typed.
And as you can see, this has the type form fields, which  is basically this object right here. So the data that we're receiving from this function is not  actually the form event that we had previously in the first example. But it's actually just an  object with the email and the password and their corresponding values.
And the reason for that,  the reason why we're not getting the form event directly is because we're not actually going to  be passing this on submit function to the form, we're actually going to make use of the handle  submit function from React hook form and pass that instead and give this function as an argument  to this handle submit. So I'm going to come here to the form. And then I'm going to do on submit,  I'm going to I'm going to give it handle submit, and then on submit, like so what this is going to  do is that whenever this form gets submitted, it's going to first call the handle submit function,  which remember comes from React hook form.
And this will do some work behind the scenes for us,  for example, it'll prevent the default behavior of the form. And then it's also going to make sure  that our form fields here are valid before then calling this on submit function with the actual  form fields. So it doesn't give us the form event, it actually just does all of the work of  validating the inputs for us.
And then once they are valid, it calls this function with the  actual form fields, which is great, because that means that we don't have to do all of that work  ourselves. And now with this, we actually have a complete form using React hook form. So now let  me go back to the actual application.
And let's see our form right here. Let me just reload. So  we get a clean slate.
And now if I press submit, we actually get a lock here in our console with  the email and the password as empty strings, which is great. Our form now works exactly asAs we  expect, but we still have more work to do. Right now, we can actually submit this form without  putting anything in either of these input fields.
Let's add some validation to these input fields  and make sure that we have at least some basic validation before we submit our form. Now, there  are multiple ways that you can validate inputs in React Hook Form. I'm going to first start by  showing you the simplest one.
The simplest way to validate an input is actually to use this register  function right here. This function takes in an optional second parameter, which can be an object  for that specific field. And in this object, you can give it some properties to define how this  field gets validated.
For example, we can give it here, required, true. This is going to make it  so that this field is required to at least have some value in it before it is considered valid.  We can then come here and add the same thing to the password field, so we'll give this here an  object, we'll do required, true.
Now, if I go back to our application and I refresh, we can  no longer submit the form. I'm pressing submit, but nothing is being logged until I actually write  one letter and one letter in this field. Now, it's going to pass the validation and we actually  get our form submission.
Besides required, you can also do a regex pattern. So you  can do that by doing pattern like this, and you can do a pattern here. Let's let Copilot  write one for us and see how that does.
Let's come back here and let's put anything here, one letter,  one letter. It's not going to let me submit it, but as soon as I type a valid email, it should  theoretically, yes, let me submit it. You can do that with a pattern too.
For the password  field, we might want to add a minimum length, which we can do with minLength. We can give this  eight, for example, and now this is going to be required to at least have eight characters. So now  if I go back to the form and I type our email.
com, I'm going to do one, two, three, four, five, six,  seven, eight. And now I'm going to do, I actually think I type nine characters, but you get the  idea. If I put anything less than eight, it's not going to submit.
We now have a more robust  validation solution in React hook form. Also, if you want to do your own custom validation,  you can also do that. So let me remove here this pattern here and let's do validate.
You can  pass it a function that will receive a value, and then you can actually write some code with  that value. For example, we can make a check that the value includes at least an at symbol. So now  if I go back to the form, I can do this and I can type any sort of invalid email.
And as long as I  put an at symbol here and I put one, two, three, four, five, six, seven, eight in the password, it  will submit the form. If I remove the at symbol, it's no longer going to submit the form. There  are actually more things that you can put in here.
Things like max length, min and max. I'm  not going to go into them on this tutorial, but I would recommend that you go and look at  the documentation for React hook form to see all of the things that you can put in here. Form  is better, right?
We can actually submit. We have proper validation, but we're not actually showing  anything to the user. If the form is invalid, I'm pressing this button here and nothing is  really happening.
Ideally, like we had it before, I want to display an error if a specific input  field is invalid. And the way that we're going to do that in React hook form is we're actually going  to access the form state from this use form hook. So besides register and handle submit, you also  get access to form state.
And this one is going to be an object that you can destructure and you can  get access to the errors object in here. This will actually have, if I hover over this, it's going  to be field errors and then form fields. This will be an object with each of our form fields  and a corresponding error if there is.
And then we can actually use this to come back here to our  JSX and right below our email input. For example, we can come in here, open a curly bracket and do  errors. email and end, and we're going to do div and we're going to put here errors.
email. message.  We'll get back to message in just a moment.
Let me just add here some text so that this error  becomes red. So we'll do class name, text red 500. Let's save this and do the same thing for  the password.
So come here, do errors. password. And now we actually are going to show an error  if there's an error in that specific form.
So now if I go back to the form and I press submit,  we actually have something that happens. Let me just close this so you can see, but we're not  actually getting any errors. We do have more space between these fields here, but we're not actually  seeing our red errors.
What's going on? Well, if you actually look at our code, we do have this  div that is being rendered. So this errors.
email must be true because otherwise this div wouldn't  rendered the same for the password. But this message here is not rendered. If I actually hover  over this message, you're going to see that this is an optional property.
This can be either string  or undefined. The thing is, we have set our errors correctly in the sense that React hook form  is handling our errors with our validation, but we haven't actually defined any message for a  specific error. So this is actually undefined.
So to fix that, we actually have to implement error  messages when our validation is incorrect. And the way that you do that with React hook form  is actually super simple. Instead of returning a Boolean here with this specific validation,  you can actually return a string instead that will get used as the error message in case that  validation fails.
So let's do it. Let's remove true. And it's your return email is required. 
Let's now do the same thing for password, remove the Boolean and do password is required. So  now if I go back to the form and I press submit, we actually have the error messages that are  being properly displayed. This is what we want.
Cool. So let's go back to our code and let's  actually complete these for the validate function and also for the minimum length. So here in this  validate function, instead of returning a Boolean, we're going to take this code and we're going to  put it here as a function and we're going to do if value includes and let copilot complete everything  for us, we're going to return this string right here.
And also here we have to return true because  in the case where the input is valid, you still have to return a Boolean. It's only when it's not  valid that you can return a string and that string will get used as the error message. In the case of  our password where we have this min length here, we still have to return a number somehow, but  we can actually do it by passing an object here.
Instead, we can do value eight and then message  password must have at least eight characters. Now if I save all of this and go back to our form,  where's our form? There we go.
And I can reload. I can press submit. It's going to say email is  required.
As soon as I type one letter, this error now changes dynamically and automatically to  email must include at. If I then put at this input is going to be considered valid and now we no  longer have an error. The same for the password.
I can type one letter and now it's going to say  password must have at least eight characters. If I can type seven more, one, two, three, four,  five, six, seven, then we have no more errors and our input is valid. I can submit this and I  can open up the console and I can see our values right here.
Now we have proper validation in our  form. This is fantastic. Now let's take it one step further and let me show you something else  that react hook form makes really, really easy to do.
Let's say that now we want to convert this  on submit function into an asynchronous function. Instead of just console logging the data directly,  we actually want to await something, maybe sending it to a server and then only once the response is  returned, do we then want to continue. And also we would want to show that to the user.
We will  want to show to the user that the form is indeed being submitted. And on top of that, we should  also disable this button while the form is being submitted. So let's do that.
Let's first convert  this function into an asynchronous function. There we go. And now I'm going to do a wait and let's  see if copilot can do it.
Yes, it can. I'm just going to await something for one second, just so  that you can actually see what's going on. React hook form makes handling asynchronous functions  really, really simple.
There's actually another property that you can access in this form state  besides errors that is going to be is submitting. This property is a Boolean that will be true  when the form is submitting. And that's all that you have to do.
You don't have to manage your own  loading state. You don't have to handle anything. React hook form is doing all of that for you.
So  then we can actually just use this property right here. We can come back to our button and we can do  disabled is submitting. And actually, let's change this text here to actually make it show something  while the form is submitting.
So I'm just going to remove this. I'm going to do is submitting,  question mark, loading, otherwise submit. If I now save this and I go back to the form and I  refresh just so we have a clean slate, I'm going to type here an at symbol because that's where our  validation requires.
And then here, 1, 2, 3, 4, 5, 6, 7, 8, I press submit. It says loading. And then  one second after we actually get the lock in our console.
Once again, I press submit loading one  second after we get the lock in our console. And just like that, our form now supports asynchronous  functions. And all that we had to do was literally just convert this to an asynchronous function and  just await something inside of it.
And React hook form takes care of the rest. Now let me show you  another really cool feature about React hook form. Let's say that instead of just awaiting here an  empty promise, we were actually sending something to a backend.
The backend can sometimes fail.  It can sometimes throw an error if the form is invalid. If this was a sign in form, maybe the  user's credentials weren't valid.
And so the backend would actually return to us an error. And  we need to handle that error. We need to show that error to the user.
And ideally, we need to plug it  inside of our form. Well, to do that, React hook form makes this super easy for us. Once again, we  can actually access another function here called set error, and this willAnd the last thing we can  do is we can pass it here an object with a message that can be, this email is already taken.
And then  the last thing that we need to do is we need to throw an error in this try block here, so that  this catch gets executed. So I'll do, throw new error. And I'm just going to do it like so.
Now,  if I go back to the form and I refresh, so we have a clean slate, I'm going to first make sure that  this form is valid. So I'll put this, 1, 2, 3, 4, 5, 6, 7, 8. When I press submit, it's going to  load for one second, and then it's going to throw this error here on the actual input field.
This  email is already taken. We've now just gotten an error from the backend, an asynchronous error, and  we've plugged it inside of our form directly using setError. Now, what if we wanted this error to  actually not belong to any specific input field, but rather to belong to the form as a whole?
Well,  we can actually do that by simply replacing this email here with root. If you saw before, let me  show you again. We have three options here.
We have email, password, and root. We can use root  for an error that does not belong to any specific input field, but rather belongs to the form as  a whole. And then all that we will need to do is come back here to our actual JSX, and then just  render out this error.
So what I'm going to do, I'm just going to take this entire block, and I'm  going to put it here right below the button. I'm going to do errors instead of password. root.
And  that's it. With just this, if I now go back to the actual application, and I close this console so  you can see, I will press submit again. This error is going to get disappeared, and now this error  appears here at the bottom.
This is how you can easily have an error that comes from the backend,  an asynchronous error that doesn't necessarily belong to any single input, and you can display it  in your form. Now, there's one more thing that I want to show you about React Hook form that you're  probably going to want to be using. Let's say that instead of having here a fresh form, a form  with nothing in it, we actually want some default values in our form, because oftentimes we're not  creating an entity, but we're editing an entity, and we want the properties of that entity to be  pre-populated in our form.
And the way that you implement that in React Hook form is actually  really simple. All that you have to do is come here where you have use form, and in this  parenthesis here, just give it an object, and you can actually specify default values, and  then you can give it here any default values that you want. For example, we can do email, and we  can do test email.
com. And note, you don't have to provide every single field in here. You can  just provide the ones where you want to have a default value.
If I now go back to the form, and I  refresh, our email field automatically has test at email pre-populated. So you can use this to super  simply add default values to your form. And now as a bonus, let me actually show you the recommended  way to use React Hook form.
There's actually a better way to handle our validation here that  is actually a lot less code, and that is going to involve using Zod as our validation library.  And for this, you're actually going to need to install two more things besides React Hook form,  because they come separate. The first one is the hook form slash resolvers library, which will give  you access to some resolvers that you can plug into your form.
And the second one is going to be  the Zod library. This is a very popular validation library. It does a lot more than just validation,  but it works really nicely with React Hook form through this resolvers library.
Then once you have  those installed, you can come back here to the form, and you can start by writing import Z from  Zod. And then we're going to use the Z to actually define a schema for our form. And I'm going to  let Copilot be very helpful and define it for us.
So we have here const schema equals Z. object. And  then this object is going to have two properties, email, which is going to be Z.
string. email.  This is very important.
And then password, Z. string. min8.
This video isn't going to be a  tutorial on Zod. I do plan on making one. I just haven't already.
So if you're watching this at a  later point, it might already be out. But for this purposes, this is all that we're going to do with  Zod. Then the next thing that we want to do is we can actually get rid of this form field because we  can infer it from the schema directly.
So what I can do is I can just do here type form fields  equals Z. infer type of schema. And actually, let's just get rid of this so we don't have  duplicates.
Basically, we have the same type here, email, string, password, string. But this is  being inferred directly from the schema here. This is also one of the benefits that you get from  using Zod.
And then in our form, we can come back here to where we have used use form. And besides  default values, we can do here resolver. And we can pass it here, Zod resolver.
We will import  this from at hook form slash resolvers, the library that we just installed. And we're going  to pass it here, the schema. What this is going to do is this is going to connect our schema to  React hook form using the Zod resolver.
And this schema from Zod actually has a lot of validation  already built in. If you just look at email here, email is going to first make sure that it's a  string, which it should be. We have an input of type text.
But then it's going to make sure  that this string is a valid email. This dot email here is going to perform all of the validation  necessary to make sure that this is a valid email, which if you remember, is a lot more than what we  did for our form. Our only validation was inside of this function here.
And we only checked that  the value has an at symbol. This dot email will actually do a lot more checks and will only  return true if this email is valid. So this is great because we did all of our work and  more with just one simple line of code.
And then for the password, we're still checking  that it's a string. And then all that we have is dot min eight. We're basically checking  that this is at least eight characters, which is exactly what we had defined here previously.
So  now because of this, we can actually remove all of our own custom validation. So we can come here  and we can remove all of this directly. And we can just use the register like this, like so.
We can  do the same thing with the password. Come here, remove everything. And now our code looks a lot  simpler because all of the validation is defined in Zod.
And we don't have to do it ourselves. If  I now open up our form again and I press submit, we actually get the same error, although it has a  different error message because now this is coming from Zod. But it's saying that string must contain  at least eight characters.
If I then put something in here, it's not going to get removed until we  have eight characters. If I remove the email here, it's going to say invalid email. I can type a  letter.
It's still invalid email. I can put an at symbol. It's still invalid email.
But as soon as  I do email. com, then the error goes away. And this is now a valid email.
This is the recommended way  to use React hook form and actually to build forms in React. Using React hook form, you get a lot  of this functionality pre-built already. You get all of the state management, error handling, and  more.
And using Zod as your validation library, you then outsource the validation to Zod so  that you don't have to do it yourself. And also, one of the little benefits of using Zod is  you no longer have to even define your own types. If you're using TypeScript, they can get  automatically inferred from Zod.
So if you ever find yourself building a form in React, consider  using React hook form and consider using Zod as your validation library. If you enjoyed this  video and you want to see more tutorials just like these, make sure to leave this video a big  thumbs up. You can also click here to subscribe.
Or you can click here to watch a different video  of mine that YouTube seems to think that you're really going to enjoy. With that being said,  my name has been Darius Cosden. This is Cosden Solutions.
Thank you so much for watching. And  I will see you in another video. Ciao, ciao.
Copyright © 2025. Made with ♥ in London by YTScribe.com