Search code examples
javascriptreactjsformsreact-hook-formyup

Conditionally making a number field required with YUP in React with react-hook-form


I have a form where if a certain radio option is selected I want to render a number input box, which should be required if the radio option is selected. Here is my YUP schema for the relevant inputs:

areSeatsLimited: yup.string().required(),
numOfSeats: yup
      .number()
      .positive("This field must contain a positive number")
      .integer("This field should contain an integer")
      .when("areSeatsLimited", {
        is: "yes",
        then: yup.number().required().typeError("The field must contain a number"),
        otherwise: yup.number().notRequired().typeError("The field must contain a number"),
      }),

This works in a sense that if I open the form for the first time and dont choose the option that seats are limited, upon submitting I get no error from the numOfSeats input, which is expected since its not required.

However, if I check the seats are limited, then it gives me the error, which is also expected since its now required. But here is the problem: when I check that seats are unlimited again after selecting that they are limited. It still throws me the error as if the field is required. Also note its throwing me the typerror message("The field must contain a number")

Here is the react code for that part of the form

<div className="radio" style={{ display: "block", marginTop: "10px" }}>
              <input
                value="no"
                type="radio"
                id="unlimited"
                {...register("areSeatsLimited")}
                checked={areSeatsLimited === "no" || areSeatsLimited === undefined ? true : false}
              />
              <label htmlFor="unlimited">Unlimited</label>
            </div>
            <div className="radio" style={{ display: "block", marginTop: "10px" }}>
              <input
                value="yes"
                type="radio"
                id="limited"
                {...register("areSeatsLimited")}
                checked={areSeatsLimited === "yes" ? true : false}
              />
              <label htmlFor="limited">Limited</label>
            </div>
            {areSeatsLimited === "yes" ? (
              <div className={`form-group required ${errors?.numOfSeats?.message ? "has-error" : ""}`}>
                <label htmlFor="numOfSeats">Number Of Seats</label>
                <input
                  type="number"
                  id="numOfSeats"
                  className="form-control"
                  placeholder="Amount of available seats..."
                  {...register("numOfSeats")}
                />
                {errors?.numOfSeats?.message ? (
                  <span style={{ color: "var(--input-error-text-color)" }}>{errors.numOfSeats.message}</span>
                ) : (
                  ""
                )}
              </div>

I have a similar set of fields but with string, and it works as expected.


Solution

  • This should solve your problem:

    otherwise: number().transform(() => {
        return undefined;
        }).nullable().notRequired(),
    

    An input field, according to this guy here, will return an empty string if you don't provide input, that is why it is throwing typeError. So you need to manually force it to return undefined when the seats are unlimited.