Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

React Hook Form Validation Errors

September 09, 2020
reacttypescript

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.

React Hook Form Validation Errors

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

Validation error

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:

Validation error summary

The UI is ugly, but you get the idea!

🏃 Play with the code

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.
Click here to share this post on Twitter

If you to learn more about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Find out more