Forms Pattern¶
Form handling with React Hook Form and Yup.
Basic Form¶
const schema = yup.object({
title: yup.string().required().max(200),
amount: yup.number().required().positive(),
date: yup.date().required(),
});
type FormData = yup.InferType<typeof schema>;
const MyForm = () => {
const { control, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: yupResolver(schema),
defaultValues: { title: "", amount: 0, date: new Date() },
});
const onSubmit = (data: FormData) => {
mutation.mutate(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="title"
control={control}
render={({ field }) => (
<TextField
{...field}
error={!!errors.title}
helperText={errors.title?.message}
/>
)}
/>
</form>
);
};
With Mutation¶
const CreateMarkerForm = () => {
const { t } = useTranslation();
const mutation = useCreateMarker();
const { control, handleSubmit, reset } = useForm<FormData>({
resolver: yupResolver(schema),
});
const onSubmit = (data: FormData) => {
mutation.mutate(data, {
onSuccess: () => {
reset();
showSuccessToast(t(TranslationKeys.markers.created));
},
});
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* fields */}
<Button type="submit" loading={mutation.isPending}>
{t(TranslationKeys.shared.button.save)}
</Button>
</form>
);
};
Validation Patterns¶
const schema = yup.object({
// Required
name: yup.string().required(),
// Optional
description: yup.string().optional(),
// Conditional
endDate: yup.date().when("hasEndDate", {
is: true,
then: (schema) => schema.required(),
}),
// Custom
email: yup.string().email().test(
"not-blocked",
"This email domain is not allowed",
(value) => !BLOCKED_DOMAINS.includes(getDomain(value))
),
});
i18n in Validation¶
const schema = yup.object({
title: yup
.string()
.required(t(TranslationKeys.validation.required))
.max(200, t(TranslationKeys.validation.maxLength, { max: 200 })),
});
Related¶
- API Layer — Mutations
- State Management — Stores