reactjsmaterial-uireact-hook-formyupmasking

Unable to get helper text to work properly with Yup validation and masking in MUI TextField with React Hook Form


I am trying to implement masking in an @mui/material TextField with react-hook-form but having trouble with the helper text to function properly. It works fine with all the other TextField inputs.

Expected behavior: When the user clicks on submit and Yup validates the form with specified schema, the helper text shows error messages for invalid input data. The error message automatically disappears afterwards when the user types proper input value which gets validated as the user types.

Problem statement: This is working for the email field, but not for the masked cell phone TextField. The error message never actually disappears and the value does not get validated once the helper text appears and the error gets generated for the first time after an on submit event is invoked.

Here's the Yup Schema:

const Schema = Yup.object().shape({
  email: Yup.string().required("Email is required").email(),
  cellPhone: Yup.number()
    .min(10, "Cell phone must have 10 digits at least")
    .max(12, "Cell phone must have 12 digits at max")
    .required("Cell phone is required")
    .typeError("Cell phone is required")
});

I tried two approaches. The first approach includes a custom component which I created using react-input-mask for masking and Controller from react-hook-form for a controlled MUI TextField:

<Controller
  name="cellPhone"
  control={control}
  render={({ field: { onChange, value }, fieldState: { error } }) => (
    <InputMask mask="+99-999-9999999" value={value} onChange={onChange}>
      {(inputProps) => (
        <TextField
          {...inputProps}
          fullWidth
          label="Cell phone"
          error={!!error}
          helperText={error?.message}
        />
      )}
    </InputMask>
  )}
/>;

The second approach included an MUI-based component available on npm, called material-ui-phone-number:

<Controller
  name="cellPhone"
  control={control}
  render={({ name, onBlur, onChange, value, fieldState: { error } }) => (
    <MuiPhoneNumber
      name={name}
      value={value}
      onBlur={onBlur}
      onChange={onChange}
      defaultCountry="us"
      style={{ width: "100%" }}
      label="Cell phone"
      variant="outlined"
      margin="normal"
      error={!!error}
      helperText={error?.message}
    />
  )}
/>;

I got the same result from both approaches and haven't been able to find the correct answer or to generate the expected outcome. Any help in resolving this is much appreciated, thanks a bunch!

I made a codesandbox for it.


Solution

  • With the help of the earlier answers and solutions provided by Arjun, I was able to implement international masking with Yup validation in react-hook-form with MUI.

    Here's how I set up my Yup schema:

    const Schema = Yup.object().shape({
      cellPhone: Yup.string()
        .required("Cell phone is required")
        .test("isValidMobile", "Cell phone is invalid", (val) => {
          // do your validation - return false to show errors
          // console.log("yup value", val);
          if (!val) {
            return false;
          }
          if (val) {
            if (isMobilePhone(val, "any", { strictMode: true })) {
              return true;
            }
            // below fixes validation for countries with 11+ digit phone numbers,
            // i.e. Pakistan, India, UAE, etc.
            const num = val.replace(/\D/g, "");
            if (num.length >= 12 && num.length <= 15) {
              return true;
            }
          }
          return false;
        }),
    });
    

    You would need these two packages as well:

    import MuiPhoneNumber from "material-ui-phone-number-2";
    import validator from "validator";
    

    Destructure the isMobilePhone function from the validator package to use it during Yup validation:

    const { isMobilePhone } = validator;
    

    Create a controlled MUI TextField with react-hook-form Controller and material-ui-phone-number-2 like this:

    <Controller
      name="cellPhone"
      control={control}
      render={({ field: { onChange }, fieldState: { error } }) => (
        <MuiPhoneNumber
          onChange={onChange}
          defaultCountry="us"
          style={{ width: "100%" }}
          label="Cell phone"
          variant="outlined"
          margin="normal"
          error={!!error}
          helperText={error?.message}
        />
      )}
    />;
    

    Special thanks to Arjun who came up with the earlier answers that lead to this solution.

    Here's the updated codesandbox for documented code and some advanced options (like detecting location from timezone to set default country in the phone number field).