How to Build a React Form Component
Tags: react, webdev, javascript
Published: 2020-09-15 17:16:53

Whether it's a login page or an internal tool, your React app is going to need a form, and handling events and dataflow via raw HTML inputs isn't any fun. This guide will walk you through how to use the [`react-hook-form` library](https://react-hook-form.com/) and take you step-by-step through a project where we create a form for an internal tool and extend it with some useful features.

By the end of this article, you’ll know how to:

* Create a simple form using `react-hook-form`
* Style your form
* Validate your form
* Add errors to your form

## Getting started / basics
<br>

*If you’re just here to snag some code, <a href="#tldr">we got you</a>.*

For this tutorial, we're working with a table that lists and orders our data, and has a nifty datepicker for sifting through the orders.

![React-form-table-example_Retool](https://retool.com/blog/content/images/2020/08/React-form-table-example_Retool.png)

Now, while we know most folks place orders online, we have to recognize that sometimes customers like to order over the phone. This means that we need to give our reps the ability to add new orders to the table.

Our React form component needs to be able to:
* Accept a customer’s name, address, the date the order was made, and an order number
* Validate the data that the customer support rep enters
* Display errors to the rep

Here is what the final product will look and feel like:

![React-Form-Table-Final_Retool](https://retool.com/blog/content/images/2020/08/React-Form-Table-Final_Retool.gif)

First things first, `react-hook-form` is a library built to handle the data in forms and do all the complicated work with validation, error handling, and submitting. There are no physical components in the library. The form component that we will build will just be made with standard `jsx` tags.

To start off, we’re going to build a simple form with no styling - it’s going to be a bunch of `textarea` inputs for the reps to fill out the customer’s name, address, the date of the order, and the order number, and, finally, a plain “submit” button. Keep in mind that `react-hook-form` uses React Hooks. Hooks are a fairly new feature to React, so if you aren’t familiar, we recommend checking out React’s [Hooks at a Glance](https://reactjs.org/docs/hooks-overview.html) docs before starting this tutorial.

After you import the `useForm()` hook, there are basic steps to run through:

1. **Use the `useForm()` hook to get `register` and `handleSubmit()`.**

You need to pass `register` into the `ref` prop when you create your form so the values the user adds—and your validation rules—can be submitted. Later on in this tutorial, we will use  `register` to handle validation. `handleSubmit()` for `onSubmit` connects your actual form into `react-hook-form` (which provides register in the first place).

```
const { register, handleSubmit } = useForm();
```

2. **Create a function to handle your data**, so your data actually winds up in your database

Your backend is your own, but we’re going to pretend that we have a `saveData()` function in another file that handles saving our data to a database. It’s just `console.log(data)` for the purposes of this tutorial.

3. **Render your form**

We’re creating a React form component, so we will use form-related `jsx` tags to build it, like `<form>`, `<h1>`, `<label>`, and `<input>`

Let’s start with a `<form>` container. Be sure to pass your `saveData()` function into `react-hook-form`’s `handleSubmit()` that you got from the `useForm()` hook and then into the `onSubmit()` in the `<form>` tag. If that sounded really confusing, peep the code below:

```
<form onSubmit={handleSubmit(data => saveData(data))}>
 ...
</form>
```

Next, let’s add a header with `<h1>` so our reps know what this form is for:

```
<form ...>
 <h1>New Order</h1>
</form>
```

We’re going to create four `<label>` and `<input>` pairs for name, address, date, and order number. For each `<input>`, be sure to pass `register` from the `useForm()` hook into the `ref` prop and give it a name in the name prop.

```
<label>Name</label>
<input name="name" ref={register} />
<label>Address</label>
<input name="address" ref={register} />
<label>Date</label>
<input name="date" ref={register} />
<label>Order Number</label>
<input name="order" ref={register} />
```
    
Finally, we’ll add a submit button by using an `<input>` with a “submit” type:

```
<input type="submit" /> 
```
        
Putting it all together, we will have the following:

```    
import React from "react";
import { useForm } from "react-hook-form";

import saveData from "./some_other_file";

export default function Form() {
 const { register, handleSubmit } = useForm();

 return (
   <form onSubmit={handleSubmit(data => saveData(data))}>
     <h1>New Order</h1>
     <label>Name</label>
     <input name="name" ref={register} />
     <label>Address</label>
     <input name="address" ref={register} />
     <label>Date</label>
     <input name="date" ref={register} />
     <label>Order Number</label>
     <input name="order" ref={register} />
     <input type="submit" />
   </form>
 );
}
```
    
Which will look like this:
    
![New-Order-React-Form_Retool](https://retool.com/blog/content/images/2020/08/New-Order-React-Form_Retool.png)
    
Cool, now we have a (kinda) working form.
    
## Styling with CSS
<br>

You can easily style your form with CSS modules, `styled-components`, or your favorite kind of styling. For our tutorial, we’re going to use `styled-components`.

First, we install and import [`style-components`](https://www.npmjs.com/package/styled-components) into our project. Then, we create a styled component (based on a `<div>`) and plop all of our pretty CSS into that. Finally, we wrap our form in the `<Styles>` tag to apply the styles. Easy!

```   
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";

import saveData from "./some_other_file";

const Styles = styled.div`
 background: lavender;
 padding: 20px;

 h1 {
   border-bottom: 1px solid white;
   color: #3d3d3d;
   font-family: sans-serif;
   font-size: 20px;
   font-weight: 600;
   line-height: 24px;
   padding: 10px;
   text-align: center;
 }

 form {
   background: white;
   border: 1px solid #dedede;
   display: flex;
   flex-direction: column;
   justify-content: space-around;
   margin: 0 auto;
   max-width: 500px;
   padding: 30px 50px;
 }

 input {
   border: 1px solid #d9d9d9;
   border-radius: 4px;
   box-sizing: border-box;
   padding: 10px;
   width: 100%;
 }

 label {
   color: #3d3d3d;
   display: block;
   font-family: sans-serif;
   font-size: 14px;
   font-weight: 500;
   margin-bottom: 5px;
 }

 .error {
   color: red;
   font-family: sans-serif;
   font-size: 12px;
   height: 30px;
 }

 .submitButton {
   background-color: #6976d9;
   color: white;
   font-family: sans-serif;
   font-size: 14px;
   margin: 20px 0px;
`;

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

 return (
   <form onSubmit={handleSubmit(data => saveData(data))}>
     <label>Name</label>
     <input name="name" ref={register} />
     <label>Address</label>
     <input name="address" ref={register} />
     <label>Date</label>
     <input name="date" ref={register} />
     <label>Order Number</label>
     <input name="order" ref={register} />
     <input type="submit" className="submitButton" />
   </form>
 );
}

export default function App() {
 return (
   <Styles>
     <Form />
   </Styles>
 );
}
```
    
That’s a lot of styling code, but look where it gets us!
    
![React-Form-Styled_Retool](https://retool.com/blog/content/images/2020/08/React-Form-Styled_Retool.png)

## Using a React component library
<br>

If you hate battling CSS, using a React component library might be a good option. It can add a lot of functionality, like animations, that are time-consuming to implement. If you’re not familiar with the plethora of React component libraries, you can [check out our recent post that covers our favorites](https://retool.com/blog/top-react-component-libraries-for-2020/). For this example, we’re going to use [Material UI](https://material-ui.com/).

The easiest way to incorporate a React component library is to use one that exposes the `ref` field as a prop. Then, all you have to do is substitute it for the `<input>` field and then pass `register` to that ref.

```   
import { Button, TextField } from "@material-ui/core";

...

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

 return (
   <>
     <h1>New Order</h1>
     <form onSubmit={handleSubmit(data => saveData(data))}>
       <label>Name</label>
       <TextField name="name" inputRef={register} />
       ...
       // Let's use Material UI's Button too
       <Button variant="contained" color="primary">Submit</Button>
     </form>
   </>
 );
}
```
    
Now, we get the sleekness and functionality of Material-UI.
 
![material-ui](https://retool.com/blog/content/images/2020/08/material-ui.gif)

## Validate your React form component
<br>

The last thing we want is for our customer support reps to add faulty data into our database. If we have any other apps using the same data, like reports running on the number of orders made in a certain time span, then adding in a date that isn’t formatted correctly could ruin the whole thing.

For our use case, we are going to add validation in the form of:
* Making all fields required
* Adding an address validator
* Validating date
* Validating order number

### Making all fields required
All you have to do to make a field required is pass an object into the `register()` prop in input that says `{required: true}`.

```    
<input name="name" ref={register({ required: true })} /> 
```
    
This will flag the `errors` prop for the “name” field, which can then be used to add an error message (see next section).

### Adding an address validator
To make our life easy, we are going to add a validator to check whether the address the user enters exists and is properly formatted.We’ll use a mock function from our example and show you how to integrate it into the React form component.

First, we define our validator function. For our purposes, we are just checking a specific string. This is where you would hook into your validator library.

```    
function addressValidator(address) {
 if (address === "123 1st St., New York, NY") {
   return true;
 }
 return false;
}
```
    
Next, we add validation to the register for address input. Make sure to pass the “value” that the user enters. If your validator function returns true, then it is validated and no error will appear.

```   
<input name="address" ref={register({
 required: true,
 validate: value => addressValidator(value),
})} />
```
    
If you want to go further with your address validation than just adding a mock function (which you probably do because this is useless in production), we recommend checking out this awesome [tutorial](https://developer.here.com/blog/street-address-validation-with-reactjs-and-here-geocoder-autocomplete) from HERE on validating location data.

### Validating date
To make sure users only enter valid dates into our date input field, we're going to add `type="date"` to our date input field in the React form component in order to force the user to fill out the field in our specified format. 
    
![React-Form-Date-Validator_Retool](https://retool.com/blog/content/images/2020/08/React-Form-Date-Validator_Retool.png)    

In some browsers (like Chrome), this will add a DatePicker to the input box. In all browsers, it will provide a clear format for the date the rep should enter and will not let them use a different format. We can even add a max date to stop the customer support rep from accidentally adding a future order date (as much as we’d all love to just skip 2020).

For this section, we’re going to use the [`moment` library](https://momentjs.com/) since it makes formatting dates much easier than JavaScript’s native date.

```    
import moment from 'moment';

...
<input
 name="date"
 type="date"
 max={moment().format("YYYY-MM-DD")}
 ref={register({ required: true })}
/>
```
    
The cool thing about validating the date in the input as opposed to the register is that we won’t have to waste time and energy building out error messages since the input will stop our user from entering an erroneous value.

![React-Form-Datepicker_Retool](https://retool.com/blog/content/images/2020/08/React-Form-Datepicker_Retool.gif)

Looking good!
    
### Validating order number
For our order number field, we need to add validation that ensures the input is a valid order number in our system. `react-hook-form` has a really easy way to apply regex validation by passing a “pattern” into the register.

Let’s say that our order numbers are always 14 integers long (though this regex could easily be updated to fit whatever your order numbers look like).

```    
<input
 name="order"
 ref={register({
   required: true,
   minLength: 14,
   maxLength: 14,
   pattern: /\d{14}/,
 })}
/>
```    

Great work! Now an error will bubble up when the order number does not meet our specified pattern. For more details, you can read more in the [`register` section](https://react-hook-form.com/api#register) of the `react-hook-form` documentation. 

### Communicate errors in your React form component
Adding error handling to your form is easy with `react-hook-form`. Let’s start with communicating that certain fields are required. All we have to do is get `errors` from the `useForm()` hook and then add a conditional to render them under the input if they are needed.

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

 return (
   <form onSubmit={handleSubmit(data => saveData(data))}>
     <h1>New Order</h1>
     <label>Name</label>
     <input name="name" ref={register({ required: true })} />
     {errors.name && "Required"}
     <label>Address</label>
     <input
       name="address"
       ref={register({
         required: true,
         validate: value => addressValidator(value)
       })}
     />
     {errors.address && "Required"}
     <label>Date</label>
     <input
       name="date"
       type="date"
       max={moment().format("YYYY-MM-DD")}
       ref={register({ required: true })}
     />
     {errors.date && "Required"}
     <label>Order Number</label>
     <input
       name="order"
       ref={register({
         required: true,
         pattern: /\d{14}/,
       })}
     />
     {errors.order && "Required"}
     <input type="submit" />
   </form>
 );
}
```
    
Notice how we refer to the error for a specific input field by using `errors.name` and `errors.date`. And here is what our error looks like:

![React-Form-Bad-Errors_Retool](https://retool.com/blog/content/images/2020/08/React-Form-Bad-Errors_Retool.gif)

One last issue - since these errors are conditionals, they’ll increase the size of our form. To get around this, we will make a simple error component that renders the height of the error, even if there is no text. We’ll also color the text red, so it’s easier to see.

```    
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";

import saveData from "./some_other_file";

const Styles = styled.div`
 background: lavender;
 ...
 .error {
   color: red;
   font-family: sans-serif;
   font-size: 12px;
   height: 30px;
 }
`;

// Render " " if no errors, or error message if errors
export function Error({ errors }) {
 return <div className={"error"}>{errors ? errors.message : " "}</div>;
}

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

 return (
   <form onSubmit={handleSubmit(data => saveData(data))}>
     <h1>New Order</h1>
     <label>Name</label>
     <input name="name" ref={register({ required: true })} />
    <Error errors={errors.name} />
     <label>Address</label>
     <input
       name="address"
       ref={register({
         required: true,
         validate: value => addressValidator(value)
       })}
     />
    <Error errors={errors.address} />
     <label>Date</label>
     <input
       name="date"
       type="date"
       max={moment().format("YYYY-MM-DD")}
       ref={register({ required: true })}
     />
     <Error errors={errors.date} />
     <label>Order Number</label>
     <input
       name="order"
       ref={register({
         required: true,
         pattern: /\d{14}/,
       })}
     />
     <Error errors={errors.order} />
     <input type="submit" className="submitButton" />
   </form>
 );
}
...
```
    
But wait! There’s no error message text to render. To fix this, let’s start with the Required validation. We do this by adding the error message for that particular type of error.
    
```
<input name="name" ref={register({ required: 'Required' })} /> 
```
    
Go through your code and change `required: true` to `required: 'Required'` in every place that you see it. Now this functions a lot more like a form we would expect to see in the real world:

![React-Form-Good-Errors_Retool](https://retool.com/blog/content/images/2020/08/React-Form-Good-Errors_Retool.gif)
    
But hold on! We validated a lot more than just required fields. Let’s get a little more granular with these errors, so our customer support reps know how to fix the problem.

### Adding an address error

To add an address error to your `validate` section, simply add an `||` so that if your validation function returns “false,” it will display your message instead.
    
```
<input
 name="address"
 ref={register({
   required: 'Required',
   validate: value => addressValidator(value) || 'Invalid address',
 })}
/>
```
    
Here is what your error will look like:

![React-form-address-error_Retool](https://retool.com/blog/content/images/2020/08/React-form-address-error_Retool.gif)

### Adding an order number error

In our system, our order numbers are always 14 digits long and made up of positive integers between 0-9. To verify this order number pattern, we are going to use `minLength` and `maxLength` to verify length and `pattern` to verify the pattern. 

First, change “minLength”, “maxLength”, and “pattern” into objects with a value key, where the regex pattern or number you defined is the value, and a `message` key, where the value is your error message.

```    
<input
 name="order"
 ref={register({
   required: 'Required',
   minLength: {
     value: 14,
     message: 'Order number too short',
   },
   maxLength: {
     value: 14,
     message: 'Order number too long',
   },
   pattern: {
     value: /\d{14}/,
     message: "Invalid order number",
   },
 })}
/>
```
    
Here is what your error will look like:

![React-form-order-error](https://retool.com/blog/content/images/2020/08/React-form-order-error.gif)
    
And that’s it for errors! Check out `react-hook-form`’s [API docs](https://react-hook-form.com/get-started#Handleerrors) for more information.

## Your React form component with `react-hook-form`
<br>
Here is our final React form component:
<br>

![React-Form-Table-Final_Retool-1](https://retool.com/blog/content/images/2020/08/React-Form-Table-Final_Retool-1.gif)

For more code samples that cover the vast range of features that react-hook-form has to offer, check out [React Hook Form](https://react-hook-form.com/)’s website. And for a full version of this code that you can test out and play around with, check out our [code sandbox](https://codesandbox.io/s/react-hook-form-get-started-o56v2?file=/src/App.js).

<h2 id="tldr">TL;DR: Syntax roundup</h2>
<br>

We know that this tutorial covered a ton of features for forms in `react-hook-form`, so just to make sure you didn’t miss anything, here is a roundup of the features we covered:

### Create a simple React form component

```
import React from "react";
import { useForm } from "react-hook-form";

import saveData from "./some-other-file";

export default function Form() {
 const { register, handleSubmit } = useForm();

 return (
   <form onSubmit={handleSubmit(data => saveData(data))}>
     <label>Field</label>
     <input name="field" ref={register} />
     <input type="submit" />
   </form>
 );
}
```
    
### Style your React form component

```
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";

import saveData from "./some_other_file";

const Styles = styled.div`
background: lavender;
 padding: 20px;

 h1 {
   border-bottom: 1px solid white;
   color: #3d3d3d;
   font-family: sans-serif;
   font-size: 20px;
   font-weight: 600;
   line-height: 24px;
   padding: 10px;
   text-align: center;
 }

 form {
   background: white;
   border: 1px solid #dedede;
   display: flex;
   flex-direction: column;
   justify-content: space-around;
   margin: 0 auto;
   max-width: 500px;
   padding: 30px 50px;
 }

 input {
   border: 1px solid #d9d9d9;
   border-radius: 4px;
   box-sizing: border-box;
   padding: 10px;
   width: 100%;
 }

 label {
   color: #3d3d3d;
   display: block;
   font-family: sans-serif;
   font-size: 14px;
   font-weight: 500;
   margin-bottom: 5px;
 }

 .submitButton {
   background-color: #6976d9;
   color: white;
   font-family: sans-serif;
   font-size: 14px;
   margin: 20px 0px;
 }
`;

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

 return (
   <form onSubmit={handleSubmit(data => saveData(data))}>
     <label>Field</label>
     <input name="field" ref={register} />
     <input type="submit" className="submitButton" />
   </form>
 );
}

export default function App() {
 return (
   <Styles>
     <Form />
   </Styles>
 );
}
```

### Validate your React form component

```
<form onSubmit={handleSubmit(data => saveData(data))}>
 <label>Name</label>
 <input name="name" ref={register({ required: true })} />
 <label>Address</label>
 <input
   name="address"
   ref={register({
     required: true,
     validate: value => addressValidator(value)
   })}
 />
 <label>Date</label>
 <input
   name="date"
   type="date"
   max={moment().format("YYYY-MM-DD")}
   ref={register({ required: true })}
 />
 <label>Order Number</label>
 <input
   name="order"
   ref={register({
     required: true,
     pattern: /\d{14}/,
   })}
 />
 <input type="submit" />
</form>
```
    
### Add errors to your React form component

```
export default function Form() {
 const { register, errors, handleSubmit } = useForm();

 return (
   <form onSubmit={handleSubmit(data => saveData(data))}>
     <label>Field</label>
     <input name="field" ref={register({ required: true })} />
     {errors.name && "Name is required"}
   </form>
 );
}
```
    
### Full form

```
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import moment from 'moment';

import saveData from "./some_other_file";

const Styles = styled.div`
 background: lavender;
 padding: 20px;

 h1 {
   border-bottom: 1px solid white;
   color: #3d3d3d;
   font-family: sans-serif;
   font-size: 20px;
   font-weight: 600;
   line-height: 24px;
   padding: 10px;
   text-align: center;
 }

 form {
   background: white;
   border: 1px solid #dedede;
   display: flex;
   flex-direction: column;
   justify-content: space-around;
   margin: 0 auto;
   max-width: 500px;
   padding: 30px 50px;
 }

 input {
   border: 1px solid #d9d9d9;
   border-radius: 4px;
   box-sizing: border-box;
   padding: 10px;
   width: 100%;
 }

 label {
   color: #3d3d3d;
   display: block;
   font-family: sans-serif;
   font-size: 14px;
   font-weight: 500;
   margin-bottom: 5px;
 }

 .error {
   color: red;
   font-family: sans-serif;
   font-size: 12px;
   height: 30px;
 }

 .submitButton {
   background-color: #6976d9;
   color: white;
   font-family: sans-serif;
   font-size: 14px;
   margin: 20px 0px;
 }
`;

export function addressValidator(address) {
 if (address === "123 1st St., New York, NY") {
   return true;
 }
 return false;
}

export function Error({ errors }) {
 return <div className={"error"}>{errors ? errors.message : " "}</div>;
}

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

 return (
   <form onSubmit={handleSubmit(data => saveData(data))}>
     <h1>New Order</h1>
     <label>Name</label>
     <input name="name" ref={register({ required: 'Required' })} />
     <Error errors={errors.name} />
     <label>Address</label>
     <input
       name="address"
       ref={register({
         required: 'Required',
         validate: value => addressValidator(value) || 'Invalid address',
       })}
     />
     <Error errors={errors.address} />
     <label>Date</label>
     <input
       name="date"
       type="date"
       max={moment().format("YYYY-MM-DD")}
       ref={register({ required: 'Required' })}
     />
     <Error errors={errors.date} />
     <label>Order Number</label>
     <input
       name="order"
       ref={register({
         required: 'Required',
         minLength: {
           value: 14,
           message: 'Order number too short',
         },
         maxLength: {
           value: 14,
           message: 'Order number too long',
         },
         pattern: {
           value: /\d{14}/,
           message: "Invalid order number",
         },
     })} />
     <Error errors={errors.order} />
     <input type="submit" className="submitButton" />
   </form>
 );
}

export default function App() {
 return (
   <Styles>
     <Form />
   </Styles>
 );
}
```

## Other React form libraries
<br>

`react-hook-form` has nearly 13K stars on [GitHub](https://github.com/react-hook-form/react-hook-form), but it's worth taking a second to explain why we decided to go with `react-hook-form` instead of other popular React form libraries, like `formik` and `react-final-form`. It’s worth recognizing that these form libraries are pretty awesome in their own ways:
* `formik` has top-notch documentation and extremely thorough tutorials.
* `react-final-form` is great for those used to working with `redux-final-form`.

Ultimately, we chose `react-hook-form` because it has a tiny bundle size, no dependencies, and is relatively new (many sources, like [LogRocket](https://blog.logrocket.com/react-hook-form-vs-formik-a-technical-and-performance-comparison/) and [ITNEXT](https://itnext.io/is-react-hook-form-the-future-57c6f94a2665), are claiming it is the best library for building forms in React) compared to the rest. If you’re interested in learning about some other ways to build React forms, [check this out](https://formspree.io/blog/react-forms-1/).