article cover

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

Reed Bargerviews icon-
react
react

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 application with 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;

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

npm i react-hook-form

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>
);
}

Additionally, for register to work properly, for each input we need to provide 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>
);
}

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>
);
}

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>
);
}

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"
/>

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 could use 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"
/>

And finally, there is validate, a custom function gives 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"
/>

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>
);
}

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",
});

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, until it’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 need either 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>
);
}

Edit on CodeSandbox



course cover
Join The 2020 JS Bootcamp Video Course 🏕️
Code ArtistryWatch Now
Reed Barger
Professional JS developer who loves to write. Here to make you a better developer, faster.