javascriptreactjsreact-hook-formzodlibphonenumber

Correct Error Message in Zod and React Hook Form


I want to validate a phone number. If it has a value then it should output "Invalid mobile number format". If it is blank, then it should output "Mobile number is required".

Currently, it always outputs as "Invalid mobile number format".

I'm using Zod, react-hook-form and libphonenumber-js

import { isValidPhoneNumber } from "libphonenumber-js";

...

const schema = zod.object({
  firstName: zod
    .string({
      required_error: "First name is required.",
    })
    .nonempty("First name is required."),
  lastName: zod
    .string({
      required_error: "Last name is required.",
    })
    .nonempty("Last name is required."),
  contactNumber: zod
    .string({
      required_error: "Mobile number is required.",
    })
    .transform((value) => `+${value}`)
    .refine(isValidPhoneNumber, "Invalid mobile number format.")
    .transform((value) => value.slice(1)),
});

...

<Controller
  control={control}
  name="contactNumber"
  rules={{ required: true }}
  render={({ field: { ref, ...field }, ...props }) => (
    <StyledPhoneInput
      enableSearch
      enableTerritories
      specialLabel="Mobile number "
      inputProps={{
        ref,
        required: true,
        placeholder: "+84",
      }}
      country={"vn"}
      autoFormat
      field={field}
      muiProps={{ required: true, fullWidth: true }}
      {...props}
    />
  )}
/>

Solution

  • I don't exactly know what constitutes a minimum valid phone number, but basically it seems you need to validate some minimum input on the contactNumber field. From what I can tell, specifying required_error prop only seems to require a defined value.

    For example, if you had just firstName: zod.string({ required_error: "First name is required." }) then the value "" is accepted while undefined throws the error that "First name is required.". The .nonempty validator appears to coerce the string into a character array and assert that it is not empty.

    You can assert the the contactNumber also has some minimal, non-zero, amount of input.

    Example:

    contactNumber: zod
      .string({
        required_error: "Mobile number is required.",
      })
      .min(1, "Mobile number is required.")
      .transform((value) => `+${value}`)
      .refine(isValidPhoneNumber, "Invalid mobile number format.")
      .transform((value) => value.slice(1)),
    

    Edit correct-error-message-in-zod-and-react-hook-form

    const schema = zod.object({
      firstName: zod
        .string({
          required_error: "First name is required.",
        })
        .nonempty("First name is required."),
      lastName: zod
        .string({
          required_error: "Last name is required.",
        })
        .nonempty("Last name is required."),
      contactNumber: zod
        .string({
          required_error: "Mobile number is required.",
        })
        .min(10, "Mobile number is too short.")
        .transform((value) => `+${value}`)
        .refine(isValidPhoneNumber, "Invalid mobile number format.")
        .transform((value) => value.slice(1)),
    });
    
    schema.parse({
      firstName: "Norman",
      lastName: "Newman",
      contactNumber: undefined, // Number is required
    });
    
    schema.parse({
      firstName: "Bob",
      lastName: "Sacamano",
      contactNumber: "5555555", // Too short
    });
    
    schema.parse({
      firstName: "Cosmo",
      lastName: "Kramer",
      contactNumber: "2125555555", // Invalid format
    });
    
    const result = schema.parse({
      firstName: "Jerry",
      lastName: "Seinfeld",
      contactNumber: "12125555555", // Valid
    });