Search code examples
reactjsreact-hook-formzod

Validation error not going away for select tags - React Hook Form & Zod


I want the validation errors displayed when a user attempts to submit the form when the select tag is empty to go away as soon as a value is selected, just like is the case with other input fields e.g. firstName

I've tried to use RHF's trigger to trigger revalidation manually but not working.

My code:

Form Schema:

const FormSchema = z.object({
  firstName: z
    .string()
    .min(1, { message: "Please provide a value" })
    .max(260, { message: "The name is too long" }),
  selectedDay: z
    .string()
    .nonempty({ message: "Please provide a value for day" })
});

RHF hook:

  const {
    register,
    formState: { errors },
    watch,
    handleSubmit,
    setValue,
    formState,
  } = useForm<PersonalInformationFormInput>({
    resolver: zodResolver(FormSchema),
    defaultValues,
  });

State:

  const [selectedDay, setSelectedDay] = useState("");
  const [days, setDays] = useState<SelectedValue[]>([]);
  const [defaultValues, setDefaultValues] =
    useState<PersonalInformationFormInput>({
      firstName: "",
      selectedDay: "",
    });

onSubmit

  const onSubmit = (data: PersonalInformationFormInput) => {
    if (!formState.isValid) {
      console.log("Validation errors:", formState.errors);
      return;
    }

    // store submitted data in local state
    setSubmittedData(data);

    // store submitted data in localStorage
    localStorage.setItem("personalInformationData", JSON.stringify(data));

    console.log("router pushing...");
    router.push("/maui-partner-center/forms/personal-info");
    console.log("router should have pushed!");
  };

handleChange:

  const handleSelectedDayChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setValue("selectedDay", e.target.value);
    setSelectedDay(e.target.value);
  };

first name (input):

          <div>
            <h2>
              First name
              <span>*</span>
            </h2>
            <input
              type="text"
              className={`h-8 w-11/12 self-center text-sm ${
                errors?.firstName?.message
                  ? "border-red-700 focus:border-none focus:ring focus:ring-red-700 focus:ring-2"
                  : "focus:border-none focus:ring focus:ring-emerald-700 focus:ring-2"
              }`}
              {...register("firstName")}
            />
            {errors?.firstName?.message && (
              <p>
                {errors.firstName.message}
              </p>
            )}
          </div>

select tag:

                    <div>
                      <select
                        className="w-full h-10 text-sm"
                        {...register("selectedDay")}
                        onChange={(e) => {
                          handleSelectedDayChange(e);
                          // trigger("selectedDay");
                        }}
                        value={selectedDay}
                      >
                        <option value="" disabled={true}>
                          Day
                        </option>
                        {days.map((dayOption) => (
                          <option key={dayOption.value} value={dayOption.value}>
                            {dayOption.label}
                          </option>
                        ))}
                      </select>
                      {errors?.selectedDay?.message && (
                        <p className="pl-4 lg:pl-8 min-[400px]:pl-6 text-red-700 mt-1">
                          {errors.selectedDay.message}
                        </p>
                      )}
                    </div>

Solution

  • You can trigger revalidation in the select tag's onChange event in addition to setting the values with setValue and setSelectedDay

    No re-validation triggered:

    const handleSelectedDayChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
        setValue("selectedDay", e.target.value);
        setSelectedDay(e.target.value);
      };
    

    Works:

    const handleSelectedDayChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
        setValue("selectedDay", e.target.value);
        setSelectedDay(e.target.value);
        trigger("selectedDay")
      };
    

    Credit: @jimmykurian