React Hook Form Validation Errors
It is crucial to display informative messages when validation checks fail so that the user can take the appropriate action. In this post, we look at different ways these validation error messages can be specified and output in React Hook Form. We will also cover how to create a generic validation error summary component.
An example
This example is similar to a previous post, where we have a form capturing a users name:
type FormData = {
name: string;
};
export default function App() {
const { register, handleSubmit } = useForm<FormData>();
const submitForm = (data: FormData) => {
console.log("Submission", data);
};
return (
<div className="app">
<form onSubmit={handleSubmit(submitForm)}>
<div>
<input
ref={register({
required: true,
minLength: 2
})}
type="text"
name="name"
placeholder="Enter your name"
/>
</div>
</form>
</div>
);
}
This time the form has two validation rules to ensure the name is populated, and it contains at least two characters.
Rendering validation errors for a field
The validation errors are stored in an errors
object in React Hook Form:
const {
register,
handleSubmit,
errors,} = useForm<FormData>();
The errors
object is in the following format:
{
<fieldName>: {
type: <ruleName>
},
...
}
An example errors
object for our form is:
{
name: {
type: "required"
}
}
There can be multiple fields with errors. There can be different errors in a particular field if it contains multiple validation rules. If there is no validation error on a field or rule, it doesn’t exist in the errors
object.
So, we can render the error messages as follows after the name input:
<input ... />
{errors.name && errors.name.type === "required" && ( <div className="error">You must enter your name</div>)}{errors.name && errors.name.type === "minLength" && ( <div className="error">Your name must be at least 2 characters</div>)}
Specifying the error message in the register
function
An alternate approach is to specify the error message in the register
function as follows:
<input
ref={register({
required: { value: true, message: "You must enter your name" }, minLength: { value: 2, message: "Your name must be at least 2 characters" } })}
type="text"
name="name"
placeholder="Enter your name"
/>
The validation error message is then available in the errors
object in a message
property as follows:
{
name: {
type: "required",
message: "You must enter your name" }
}
So, we can change the way we render the error message to the following:
<input ... />
{errors.name && ( <div className="error">{errors.name.message}</div>)}
This is nice if the error messages are configurable and fetched from a server and pushed into the form.
Using the ErrorMessage
component
It will arguably be cleaner if we have a generic component for a validation error. Luckily, this already exists in React Hook Form in the @hookform/error-message
package. The component is called ErrorMessage
, and we can use this as follows:
<input
name="name"
...
/>
<ErrorMessage errors={errors} name="name" />
So, we pass all the errors into ErrorMessage
and tell it which field to show errors for using the name
property.
This component just renders a string by default. We can tell it what element to place the error message in using the as
property:
<ErrorMessage
errors={errors}
name="name"
as="span"/>
So, this will render the error message in a span
element.
We can also use as
to render the message in our own component:
<ErrorMessage
errors={errors}
name="name"
as={<ErrorMessageContainer />}/>
We can then specify styles in the container component:
type ErrorMessageContainerProps = {
children?: React.ReactNode;
};
const ErrorMessageContainer = ({ children }: ErrorMessageContainerProps) => (
<span className="error">{children}</span>
);
Nice!
Rendering a validation error summary
What if we want to render all the errors together after the submit button? This is useful for larger forms, when fields may be off the top of the screen when the submit button is pressed.
Let’s build a generic component for this called ErrorSummary
. We will consume this under the submit button as follows:
<div>
<button type="submit">Submit</button>
</div>
<ErrorSummary errors={errors} />
First, we will add an age field to our form to give the generic component a good test.
type FormData = {
name: string;
age: number;};
export default function App() {
...
return (
<div className="app">
<form onSubmit={handleSubmit(submitForm)}>
...
<div> <input ref={register({ required: { value: true, message: "You must enter your age" }, min: { value: 30, message: "You must be at least 30" } })} type="number" name="age" placeholder="Enter your age" /> <ErrorMessage errors={errors} name="age" as={<ErrorMessageContainer />} /> </div> ...
</form>
</div>
);
}
We are still rendering errors after each field. The requirement is to render all the errors again after the submit button.
Let’s start the implementation for ErrorSummary
:
type ErrorSummaryProps<T> = {
errors: FieldErrors<T>;
};
function ErrorSummary<T>({
errors,
}: ErrorSummaryProps<T>) {}
The component contains an errors
prop, which will contain all the errors. FieldErrors
is a type that represents the errors
object from React Hook Form.
We shall return null
when there are no errors to output:
function ErrorSummary<T>({
errors,
}: ErrorSummaryProps<T>) {
if (Object.keys(errors).length === 0) { return null; }}
We can then iterate and render each error:
function ErrorSummary<T>({ errors }: ErrorsProps<T>) {
if (Object.keys(errors).length === 0) {
return null;
}
return ( <div className="error-summary"> {Object.keys(errors).map((fieldName) => ( <ErrorMessage errors={errors} name={fieldName as any} as="div" key={fieldName} /> ))} </div> );}
We use the ErrorMessage
component to render each error. At the moment, I’m asserting fieldName
to be any
because I couldn’t find an appropriate available type in React Hook Form.
So, we now have a validation summary beneath the submit button when the form is invalid:
The UI is ugly, but you get the idea!
Wrap up
React Hook Form gives us the flexibility to render errors generically. The generic validation summary component we have created can be used with any React Hook Form.
Did you find this post useful?
Let me know by sharing it on Twitter.If you to learn more about using TypeScript with React, you may find my course useful: