TL;DR Codesandbox to see it in action
Introduction
In this tutorial I will show you how I managed to use multiple Yup validation schemas with react-hook-form.
My use case I had a form which I wanted to save (with all fields required) but also to save as draft (with only one required field)
Step 1: Implement default schema for save
import { useForm } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
const schema = yup.object().shape({
firstName: yup.string().required(),
lastName: yup.string().required()
});
/*...*/
const {
register,
handleSubmit,
formState: { errors }
} = useForm<FormData>({
resolver: yupResolver(schema), // default schema
});
const onSubmit = handleSubmit((_data) => {
alert("schema valid!");
});
<form onSubmit={onSubmit}>
<label>First Name</label>
<input {...register("firstName")} />
<label>Last Name</label>
<input {...register("lastName")} />
<button type="submit">
Save
</button>
</form>
Step 2: Implement second schema for save as draft
const draftSchema = yup.object().shape({
firstName: yup.string().required(),
lastName: yup.string() // lastName is optional for draft
});
/*...*/
const saveDraft = async () => {
// todo
}
<form onSubmit={onSubmit}>
{/*...*/}
<button type="button" onClick={saveDraft}>
Save as draft
</button>
</form
Step 3: saveDraft function
We're going to manually use the yup.validate() to validate the draftSchema when clicking save as draft. And setting the error into react-hook-form.
/*...*/
const {
register,
handleSubmit,
getValues, // to get current form data
setError, // to set error from yup.validate()
} = useForm<FormData>({
resolver: yupResolver(schema),
});
const saveDraft = async () => {
const data = getValues();
// try catch block for yup validate function. abortEarly: false so it returns all errors on our form.
try {
await draftSchema.validate(data, { abortEarly: false });
// if passes, data is valid
alert("draft schema valid!");
} catch (error) {
// loop over all errors and setError to use them in react-hook-form
error.inner?.map((inner, index) => {
const { type, path, errors } = inner;
return setError(path, { type, message: errors[index] });
});
}
};
/*...*/
Conclusion
And that's it! Not the most beautiful solution, but the only one I've got working.
I hope you've found this article useful, please let me know how you used this or another solution in the comments :)
Top comments (4)
Thanks!
I've found more natural way to do it:
Not the yup resolver but you can easily refactor it.
And formState.isSubmitting will properly reflect submit status, but for your solution it will be always false.
@bublinec Glad you found it useful!
This is just what I was looking for, thanks! :)