Master-detail forms with React Hook Form
In the last couple of posts, we have built a form to capture a name, an email address, and a score with some reasonably complex validation rules.
- Getting started with React Hook Form with TypeScript
- Custom validation rules in React Hook Form
- Master-detail forms with React Hook Form (this post)
In this post, we will capture multiple scores from a person and turn our form into a master-detail form. We will start with the form we implemented in the last post.
Using useFieldArray
We are going to use React Hook Form’s useFieldArray
to help render and manage the scores. So, let’s import this:
import {
useForm,
useFieldArray} from "react-hook-form";
We are going to capture more than one score now, so, let’s change the field name to scores and change it to an array:
type PersonScore = {
name: string;
email: string;
scores: number[];};
useFieldArray
needs to use the control
object from useForm
, which is the controller for the form. So, let’s destructure this:
const {
register,
handleSubmit,
errors,
control} = useForm<PersonScore>();
We can now use useFieldArray
:
const { fields, append, remove } = useFieldArray(
{
control,
name: "scores"
}
);
We have destructured the following from useFieldArray
:
fields
. This is an array that will help us render each score fieldappend
. This is a function that will allow us to add score fieldsremove
. This is a function that will enable us to remove score fields
Ensuring there is at least one score
We want to start with a single score field. So, let’s use the append
function to add a score to the array:
if (fields.length === 0) {
append({});
}
The function takes in an object containing default values for the fields. We have no default values, so we have passed an empty object.
Rendering score fields
Let’s move on to render the score fields:
{
fields.map((score, index) => (
<div key={score.id}>
<div className="field">
<label htmlFor={`scores[${index}]`}>
Score{` ${index + 1}`}
</label>
<input
type="number"
id={`scores[${index}]`}
name={`scores[${index}]`}
ref={register({
required: true,
min: 0,
max: 100,
validate: isEven
})}
/>
</div>
</div>
));
}
We are mapping over the fields
array that contains all the scores. We render a label
and input
for each score.
Notice that we use the id
property from the items in the fields
array as the React component key. This is a guid that useFieldArray
gives us.
We need to name the fields score[0], score[1], score[2], … so that useFieldArray
can manage them.
Our validation rules remain the same as the previous implementation of the form from the last post. Let’s render the validation error messages:
{
errors.scores &&
errors.scores[index] &&
errors.scores[index].type === "required" && (
<div className="error">
You must enter your score.
</div>
);
}
{
errors.scores &&
errors.scores[index] &&
errors.scores[index].type === "min" && (
<div className="error">
You score must be at least 0
</div>
);
}
{
errors.scores &&
errors.scores[index] &&
errors.scores[index].type === "max" && (
<div className="error">
Your score must be no more than 100
</div>
);
}
{
errors.scores &&
errors.scores[index] &&
errors.scores[index].type === "validate" && (
<div className="error">
Your score must be and even number
</div>
);
}
Notice that the scores
errors are now in an array. Apart from that, the structure of each array is the same as a regular field.
Buttons to add and remove score fields
Let’s add a button to add a score field:
<button
className="add"
onClick={(e: React.MouseEvent) => {
e.preventDefault();
append({});
}}
>
Add score
</button>
When the button is clicked, we call the append
function from useFieldArray
to add a new score.
Notice that we have called the events preventDefault
method to ensure the form isn’t submitted when the button is pressed.
Let’s also add a button to remove a score field:
<button
className="remove"
onClick={(e: React.MouseEvent) => {
e.preventDefault();
remove(index);
}}
>
Remove
</button>
When the button is clicked, we call the remove
function from useFieldArray
passing in the index of the score to remove.
That’s it. A working version of this form is available on CodeSandbox
Wrap up
We’ve reached the end of this series of posts on React Hook Form, where we have used it to implement some complex but common form requirements. React Hook Form is a great form library that is simple to use and also very performant - well worth giving it a try when implementing your next form!
If you to learn more about using TypeScript with React, you may find my course useful: