Why isn’t my Zod validation error displaying in React Hook Form when validating an array of strings?

I’m using React Hook Form with Zod validation to implement an OTP (One-Time Password) form where users input a 6-digit OTP, with each digit in a separate input field. I’m using z.object to validate that the OTP consists of exactly 6 numeric digits, and I expect validation errors to display when incorrect input is provided or when less than 6 input is submitted .

Here’s my otpSchema and the relevant code:

const otpSchema = z.object({
  otp: z
    .array(
      z.string().regex(/^d$/, 'Each input must be a single digit'), // Ensure it is a number
    )
    .length(6, 'OTP must consist of exactly 6 digits')
    .nonempty({
      message: "Can't be empty!",
    }),
});

const {
  register: verifyOtpRegister, //unused
  handleSubmit: verifyOtpSubmit,
  trigger: verifyOtpTrigger,
  control,
  getValues: getVerifyOtpValues,
  formState: { errors: verifyOtpErrors },
} = useForm({
  resolver: zodResolver(otpSchema),
});

const onVerifyOtp = (data?: any) => {
  console.log({ error: verifyOtpErrors.otp });
  console.log({ data });
  verifyOtp();
};

I’m rendering 6 separate input fields for the OTP, like this:

{[...Array(6)].map((_, index) => (
  <Grid item key={index}>
    <Controller
      name={`otp[${index}]`}
      control={control}
      render={({ field }) => (
        <TextField
          {...field}
          id={`otp-input-${index}`}
          inputMode="numeric"
          inputProps={{
            maxLength: 1,
            pattern: '[0-9]*',
          }}
          onChange={(e) => {
            console.log(e);
            handleInputChange(e, index, field);
          }}
          onKeyDown={(e) => handleKeyDown(e, index)}
          value={field.value || ''}
          autoComplete="off"
          sx={{ width: 40, textAlign: 'center' }}
        />
      )}
    />
  </Grid>
))}

<FormHelperText error>
  {`${verifyOtpErrors?.otp?.message || ''}`}
</FormHelperText>

<button
  type="submit"
  className="bg-blue-500 text-white py-2 px-4 rounded mt-4"
  onClick={() => {
    console.log(verifyOtpErrors?.otp);
    verifyOtpSubmit(onVerifyOtp);
  }}
>
  Verify OTP
</button>

The issue:

  • When I input the correct valid OTP format (e.g., 123456), the flow works, and the form successfully validates and submits.
  • However, when I enter invalid data (e.g., adb123, empty fields, or less/more than 6 digits), the flow doesn’t work as expected but validation doesn’t display any error messages. The errors.otp object is logged as undefined, and no error is shown in the FormHelperText.

What I’ve tried:

  • Added a console.log(verifyOtpErrors?.otp) in the onVerifyOtp function to debug, but verifyOtpErrors?.otp is always undefined for invalid input.
    Used verifyOtpTrigger('otp') to manually trigger validation, but it still doesn’t populate the error messages and is undefined.
    Ensured that the name in Controller matches the schema (otp[index]).