Carl Rippon

Building SPAs

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

React Hook Form Server-side Validation

September 16, 2020
reacttypescript

For security reasons, a form always needs to validated on the server in addition to on the client. Certain validation rules may only be executed on the server - particularly when the rules depend on the database or another web service. If one of these server validation rules fail, we still need to inform the user. So, how do we deal with server-side validation in React Hook Form? Let’s find out.

React Hook Form Server-side Validation

An example

We have a simple form that has been implemented using React Hook Form. The form captures a users name:

type FormData = {
  name: string;
};
export default function App() {
  const { register, handleSubmit, errors } = useForm<FormData>();
  const submitForm = async (data: FormData) => {
    const result = await postData(data);
  };
  return (
    <div className="app">
      <form onSubmit={handleSubmit(submitForm)}>
        <input
          ref={register({
            required: { value: true, message: "You must enter your name" }
          })}
          type="text"
          name="name"
          placeholder="Enter your name"
        />
        <ErrorSummary errors={errors} />
      </form>
    </div>
  );
}

The name field on the form is mandatory. We output a validation error if this hasn’t been populated, using the errors object from React Hook Form and the ErrorSummary component we created in the last post.

The form submission logic calls a postData function which sends the form to the server. Our mock implementation of postData is below:

type postDataResult<T> = {
  success: boolean;
  errors?: { [P in keyof T]?: string[] };
};
const postData = ({
  name,
}: FormData): Promise<postDataResult<
  FormData
>> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      if (name === "Fred") {
        resolve({
          success: false,
          errors: {
            name: ["The name must be unique"],
          },
        });
      } else {
        resolve({ success: true });
      }
    }, 100);
  });
};

This simulates a call to the server. The important bits are:

  • If the submission is successful, an object containing success equal to true is returned in the promise.
  • If validation errors occur on the server, they are returned in the promise in the following format in an errors property:
{
  fieldName: ["error1", "error2", ... ],
  ...
}

This is the validation error format in ASP.NET Core.

Integrating the server validation errors into React Hook Form

We need to inform the user when a server validation error happens. To do this, we need to add the error to the errors object in React Hook Form. We can do this using the setError method from React Hook Form. So, let’s destructure this from the useForm hook:

const {
  register,
  handleSubmit,
  errors,
  setError,} = useForm<FormData>();

React Hook Form’s error format is:

{
  fieldName: {
    ruleName: error
  },
  ...
}

So, we can iterate through the server validation errors, calling the setError method, mapping to the React Hook Form’s error format. We can create the following utility function to do this:

function addServerErrors<T>(
  errors: { [P in keyof T]?: string[] },
  setError: (
    fieldName: keyof T,
    error: { type: string; message: string }
  ) => void
) {
  return Object.keys(errors).forEach((key) => {
    setError(key as keyof T, {
      type: "server",
      message: errors[key as keyof T]!.join(
        ". "
      ),
    });
  });
}

The utility function is generic and can work on any form with this server error format.

We use a mapped type, { [P in keyof T]?: string[] }, to represent the errors from the server.

We pass the setError function from React Hook Form into our utility function.

We iterate through the keys in the server error calling setError, setting the rule name to “server” and the error to the concatination of all the server errors for the key.

We have to do some fiddling to keep the TypeScript compiler happy. We have to assert key is a key of the type passed in because TypeScript infers it to be a string. We also have to remind TypeScript that the error isn’t null or undefined with the non-null assertion operator (!).

We can then use this utility function in the submit function after we have the response from the server:

const submitForm = async (data: FormData) => {
  const result = await postData(data);
  if (!result.success && result.errors) {    addServerErrors(result.errors, setError);  }};

The ErrorSummary summary will now show errors from the server:

Server validation error

Neat!

🏃 Play with the code

Wrap up

React Hook Form makes integrating serverside validation simple with its setError function. Implementing a generic utility function that maps a server error format to React Hook Forms format means that this can be used in any form that interacts with that server.

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