Skip to content

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