DEV Community

loading...
Cover image for How to Use React-Hook-Form for Dead-Simple Forms

How to Use React-Hook-Form for Dead-Simple Forms

reedbarger profile image Reed Barger Originally published at reedbarger.com on ・7 min read

Nobody enjoys creating and re-creating forms with validation, React developers included.

It’s great to find a form library that provides a lot of convenient tools and doesn’t require much code.

Based off of those two criteria, utility and simplicity, the best React form library I’ve come across is react-hook-form.

This article is going to be dedicated to getting up and running with reactive form so you can use it in your own projects and see how easy it is to use the

You can find the documentation for the library at react-hook-form.com.

React Hook Form Homepage

Once there, you’ll find a comparison between the library as well as two primary competitors: Formik, and Redux Form. As you’ll see, it requires a lot less code to create the same functionality. The library is more performant than the others in that it uses uncontrolled components, which results in a lot less re-rendering as compared to its competitor libraries.

Installing react-hook-form

In our example, let’s cover a user signing up to our react applicationwith three inputs for their username, password, and email.

import React from "react";

const styles = {
  container: {
    width: "80%",
    margin: "0 auto",
  },
  input: {
    width: "100%",
  },
};

function App() {
  return (
    <div style={styles.container}>
      <h4>My Form</h4>
      <form>
        <input placeholder="Username" style={styles.input} />
        <input placeholder="Email" style={styles.input} />
        <input placeholder="Password" style={styles.input} />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Once we have a React project up and running, we’ll start by installing the reack-hook-form library.

npm i react-hook-form
Enter fullscreen mode Exit fullscreen mode

useForm hook + register

And once we’ve done that, we just need to call the useForm hook. From it, we’ll get back an object from which will destructure register.

register is a function, which we need to connect to each one of the input refs. It takes the value typed into each input and makes it available for validation and for the form to be submitted with that data:

function App() {
  const { register } = useForm();

  return (
    <div style={styles.container}>
      <h4>My Form</h4>
      <form>
        <input ref={register} placeholder="Username" style={styles.input} />
        <input ref={register} placeholder="Email" style={styles.input} />
        <input ref={register} placeholder="Password" style={styles.input} />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Additionally, for register to work properly, for each input we need toprovide a name attribute, which is what the value typed in is going to be set to for the fields to username, email and password respectively.

function App() {
  const { register } = useForm();

  return (
    <div style={styles.container}>
      <h4>My Form</h4>
      <form>
        <input
          name="username"
          ref={register}
          placeholder="Username"
          style={styles.input}
        />
        <input
          name="email"
          ref={register}
          placeholder="Email"
          style={styles.input}
        />
        <input
          name="password"
          ref={register}
          placeholder="Password"
          style={styles.input}
        />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

handleSubmit

Then to handle submitting our form and receiving the input data, we’ll add an onSubmit to our form element and connect it to a local function called the same name.

function App() {
  const { register } = useForm();

  function onSubmit() {}

  return (
    <div style={styles.container}>
      <h4>My Form</h4>
      <form onSubmit={onSubmit}>
        <input
          name="username"
          ref={register}
          placeholder="Username"
          style={styles.input}
        />
        <input
          name="email"
          ref={register}
          placeholder="Email"
          style={styles.input}
        />
        <input
          name="password"
          ref={register}
          placeholder="Password"
          style={styles.input}
        />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And from use form, we’ll grab a function called handleSubmit and wrap it around onSubmit as a higher-order function. It will take care of collecting all of our data typed into each input which we’ll receive within onSubmit as an object called data.

Now if we console.log(data) we can see what we typed into each of our inputs on a property with the same name:

function App() {
  const { register, handleSubmit } = useForm();

  function onSubmit(data) {
    console.log(data); // { username: 'test', email: 'test', password: 'test' }
  }

  return (
    <div style={styles.container}>
      <h4>My Form</h4>
      <form onSubmit={handleSubmit(onSubmit)}>
        <input
          name="username"
          ref={register}
          placeholder="Username"
          style={styles.input}
        />
        <input
          name="email"
          ref={register}
          placeholder="Email"
          style={styles.input}
        />
        <input
          name="password"
          ref={register}
          placeholder="Password"
          style={styles.input}
        />
        <button type="submit">Submit</button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Validation options with register

To validate our form and add constraints for each input’s value is very simple—we just need to pass information to the register function.

register accepts an object, as well as a number of properties. The first one is required.

By default it’s set to false but we can set that to true to make sure form isn’t submitted if it’s not filled out.

For username, we want that to be required, and we want our users’ usernames to be more than six characters but less than 24.

For that, we can set constraint of minLength to six, but the maxLength should be 20.

<input
  name="username"
  ref={register({
    required: true,
    minLength: 6,
    maxLength: 20,
  })}
  style={styles.input}
  placeholder="Username"
/>
Enter fullscreen mode Exit fullscreen mode

If we were using numbers for this input (say if this was for the person’s age).We would set min and max, instead of minLength and maxLength to whatever we wanted.

Next we can supply a regex pattern if we like. So for example, if we wanted a username to only contain uppercase and lowercase characters, we coulduse the following regex which allows for custom validation.

<input
  name="username"
  ref={register({
    required: true,
    minLength: 6,
    maxLength: 20,
    pattern: /^[A-Za-z]+$/i,
  })}
  style={styles.input}
  placeholder="Username"
/>
Enter fullscreen mode Exit fullscreen mode

And finally, there is validate, a custom functiongives us access to the value typed into the input and to provide our own logic to determine whether it is valid or not (by returning the boolean true or false).

For the email here, we also want it to be required and for it to be a valid email. To check this, we can pass the input to a function from the library validator called isEmail.

If the input is an email, it return true. Otherwise, false.

<input
  name="email"
  ref={register({
    required: true,
    validate: (input) => isEmail(input), // returns true if valid
  })}
  style={styles.input}
  placeholder="Email"
/>
Enter fullscreen mode Exit fullscreen mode

For password’s register function, we’ll set required to true, minlength to six, and we won’t set a maxLength

Displaying errors

Right now, if an input within our form isn’t valid, we don’t show anything to the user. The form data is merely not submitted (onSubmit isn’t called) and the first input with an error is automatically focused, which doesn’t provide our user any detailed feedback about what’s happening.

Instead of just not submitting the form we can grab an errors object from useForm.

And just like the data function we get in onSubmit, errors contains properties corresponding to each of the inputs names if it has an error.

For our example, we can add a conditional to each of the inputs and say if there is an error, we’ll set the borderColor to red.

function App() {
  const { register, handleSubmit, errors } = useForm();

  function onSubmit(data) {
    console.log(data);
  }

  return (
    <div style={styles.container}>
      <h4>My Form</h4>
      <form onSubmit={handleSubmit(onSubmit)}>
        <input
          name="username"
          ref={register({
            required: true,
            minLength: 6,
            maxLength: 20,
            pattern: /^[A-Za-z]+$/i,
          })}
          style={{ ...styles.input, borderColor: errors.username && "red" }}
          placeholder="Username"
        />
        <input
          name="email"
          ref={register({
            required: true,
            validate: (input) => isEmail(input),
          })}
          style={{ ...styles.input, borderColor: errors.email && "red" }}
          placeholder="Email"
        />
        <input
          name="password"
          ref={register({
            required: true,
            minLength: 6,
          })}
          style={{ ...styles.input, borderColor: errors.password && "red" }}
          placeholder="Password"
        />
        <button type="submit" disabled={formState.isSubmitting}>
          Submit
        </button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Validation mode

You’ll notice, by default, the errors object is updated only when we submit the form. The default validation is only performed upon submitting the form.

We can change this by passing useForm an object, where we can set the mode to when we want validation to be performed: onBlur, onChange, or onSubmit. onBlur is going to make validation run whenever the user ‘blurs’ or clicks away from the input. onChange is whenever a user types in the input and onSubmit is whenever the form submitted.

Here let’s select onBlur.

const { register, handleSubmit, errors } = useForm({
  mode: "onBlur",
});
Enter fullscreen mode Exit fullscreen mode

Note that there are other helpers to both set and clear the errors manually, (setError and clearError). These would be used if, for example, you had certain cases where you want it to create a different error or clear an error yourself within onSubmit.

formState

The last value which we can get the useForm hook is formState.

It gives us important information such as when certain inputs have been touched, as well as when the form has been submitted.

So if you want to disable your form’s button to make sure the form isn’t additionally isn’t submitted more times than it needs to, we could set, disabled to formState.isSubmitting.

Whenever we’re submitting our form it’s going to be disabled, untilit’s done with validation and running our onSubmit function.

Conclusion

This is just a quick primer on using the react-hook-form library. I’ve really enjoyed using it in several of my own projects.

I’d highly recommend you giving it a shot yourself for wherever you needeither simple or advanced form validation. There are a ton more features from the library’s API that I didn’t cover here. You can dig into the documentation website and it should cover any use case you can think of.

Final Code

import React from "react";
import { useForm } from "react-hook-form";
import isEmail from "validator/lib/isEmail";

const styles = {
  container: {
    width: "80%",
    margin: "0 auto",
  },
  input: {
    width: "100%",
  },
};

export default function App() {
  const { register, handleSubmit, errors, formState } = useForm({
    mode: "onBlur",
  });

  function onSubmit(data) {
    console.log(data);
  }

  return (
    <div style={styles.container}>
      <h4>My Form</h4>
      <form onSubmit={handleSubmit(onSubmit)}>
        <input
          name="username"
          ref={register({
            required: true,
            minLength: 6,
            maxLength: 20,
            pattern: /^[A-Za-z]+$/i,
          })}
          style={{ ...styles.input, borderColor: errors.username && "red" }}
          placeholder="Username"
        />
        <input
          name="email"
          ref={register({
            required: true,
            validate: (input) => isEmail(input),
          })}
          style={{ ...styles.input, borderColor: errors.email && "red" }}
          placeholder="Email"
        />
        <input
          name="password"
          ref={register({
            required: true,
            minLength: 6,
          })}
          style={{ ...styles.input, borderColor: errors.password && "red" }}
          placeholder="Password"
        />
        <button type="submit" disabled={formState.isSubmitting}>
          Submit
        </button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Edit on CodeSandbox

Enjoy this post? Join The React Bootcamp

The React Bootcamp takes everything you should know about learning React and bundles it into one comprehensive package, including videos, cheatsheets, plus special bonuses.

Gain the insider information hundreds of developers have already used to master React, find their dream jobs, and take control of their future:

The React Bootcamp
Click here to be notified when it opens

Discussion (3)

pic
Editor guide
Collapse
gandalfarcade profile image
Chris Mumford

I've found react-hook-form to be easiest to use so far. However, regardless of what framework I use, I usually end up having to implement a custom solution for more complex forms. E.g. handling linked conditional required inputs. Say you've got a form with two inputs and a value in either one will make the form valid. I haven't found a react forms library that supports this out of the box.

Collapse
bluebill1049 profile image
Bill

Hey Chris,

Thanks for your feedback. React Hook Form's philosophy is keep API simple and primary and let users compose and expand the functionality, hence we only and try to keep one page API documentation. Let me know at Github if you have any implementation problems, i am more than happy to help.

cheers
bill

Collapse
bluebill1049 profile image
Bill

Thanks, Reed very much for writing this blog post.