React Hook Form Server-side Validation
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.
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 totrue
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:
Neat!
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.If you to learn more about using TypeScript with React, you may find my course useful: