Optional field validation in Yup schema
Asked Answered
L

3

10

I'm using react-hook-form with yup for my form validation and want some fields to be optional (null).

Following their documentation, I'm using nullable() and optional() but it is still getting validated:

export const updateAddressSchema = yup.object({
  address: yup
    .string()
    .nullable()
    .optional()
    .min(5, "Address must be more than 5 characters long")
    .max(255, "Address must be less than 255 characters long"),
  city: yup
    .string()
    .nullable()
    .optional()
    .max(32, "City name must be less than 32 characters long"),
  postal_code: yup
    .string()
    .nullable()
    .optional()
    .length(10, "Postal code must be 10 characters long"),
  phone: yup
    .string()
    .nullable()
    .optional()
    .min(10, "Phone number must be more than 10 characters long")
    .max(20, "Phone number must be less than 20 characters long"),
});

Is there any right way to do this?

Leucas answered 26/5, 2022 at 9:42 Comment(5)
Its working fine, you have condition if the field is left empty then it will not validate, else it will validate because field has min, max condition. Either its empty or fall between min, max valuesAguilera
Well, yes it doesn't validate the empty field. But it does validate the min and max values. I'm looking for a way so it doesn't validate against min, max when the field is empty.Leucas
When field is empty it does not validate. See this exampleAguilera
@Aguilera This is mostly weird. In my own app it does get validated against the length and min/max even when it's empty. Maybe it's because I'm using react-hook-form and not formikLeucas
True its not working with react-hook-forms, There may be other workaround for this, i'll lookAguilera
L
4

Thanks a lot to @Usama for their answer and solution!

I experienced another problem when using their solution. My back-end API disregards null values and returns the previous value if null values are submitted. The problem was that on initial render the text field's value was null but after selecting and typing and then deleting the typed letters to get it empty again (without submitting), its value would change to an empty string and so my API would throw an error and wouldn't update the user info.

The way I managed to fix it was to use yup's .transform() method to transform the type from empty string to null if the text field wasn't filled:

export const updateAddressSchema = yup.object().shape(
  {
    address: yup.string().when("address", (value) => {
      if (value) {
        return yup
          .string()
          .min(5, "Address must be more than 5 characters long")
          .max(255, "Address must be less than 255 characters long");
      } else {
        return yup
          .string()
          .transform((value, originalValue) => {
            // Convert empty values to null
            if (!value) {
              return null;
            }
            return originalValue;
          })
          .nullable()
          .optional();
      }
    }),
    ......................
  },
  [
    ["address", "address"],
    ......................,
  ]
);

I really hope this helps someone.

Leucas answered 27/5, 2022 at 13:31 Comment(0)
A
15

You need to use .when for conditional validation like this below. I have added only for address and city only, you can add for other like this.

export const updateAddressSchema = yup.object().shape({

  address: yup.string().when("address", (val, schema) => {
       if(val?.length > 0) {  //if address exist then apply min max else not
          return yup.string().min(5, "min 5").max(255, "max 255").required("Required");
       } else { 
          return yup.string().notRequired();
       }
  }),

  city: yup.string().when("city", (val, schema) => {
       if(val?.length > 0) {
          return yup.string().max(32, "max 32").required("Required");
       }
       else { 
          return yup.string().notRequired();
       }
  }),
  
 }, [
     ["address", "address"], 
     ["city", "city"], 
    ]                   //cyclic dependency
 );

Also, you need to add Cyclic dependency

Aguilera answered 27/5, 2022 at 8:46 Comment(5)
Thanks a lot for your answer. It does seem to work but another weird problem I'm facing right now is that when on initial render the text fields are empty and I submit, it doesn't get validated as it should, but the moment I select and deselect a field and hit submit, it gets validated again. It seems like upon focus the text field's value changes. I might try Formik altogether if I don't find a solution for that unexpected behavior.Leucas
Then, I think you need to check length of text as well, if length > 0 then validate, else not. I have updated my answer, you can check again.Aguilera
Your answer does work and I'm gonna mark it as the right solution, yet I just found out that this problem comes from my API where it doesn't accept empty string values and requires them to be null for them to get updated. I need to find a way to convert empty string values to null before submitting.Leucas
Right. In your submit function you can check if value length is 0 then set it as null.Aguilera
This is very stupid actually. To have so much code just for an optional field. Not to mention the extra array at the end to handle cyclic dependency.Cower
L
4

Thanks a lot to @Usama for their answer and solution!

I experienced another problem when using their solution. My back-end API disregards null values and returns the previous value if null values are submitted. The problem was that on initial render the text field's value was null but after selecting and typing and then deleting the typed letters to get it empty again (without submitting), its value would change to an empty string and so my API would throw an error and wouldn't update the user info.

The way I managed to fix it was to use yup's .transform() method to transform the type from empty string to null if the text field wasn't filled:

export const updateAddressSchema = yup.object().shape(
  {
    address: yup.string().when("address", (value) => {
      if (value) {
        return yup
          .string()
          .min(5, "Address must be more than 5 characters long")
          .max(255, "Address must be less than 255 characters long");
      } else {
        return yup
          .string()
          .transform((value, originalValue) => {
            // Convert empty values to null
            if (!value) {
              return null;
            }
            return originalValue;
          })
          .nullable()
          .optional();
      }
    }),
    ......................
  },
  [
    ["address", "address"],
    ......................,
  ]
);

I really hope this helps someone.

Leucas answered 27/5, 2022 at 13:31 Comment(0)
E
1
namespace: yup.lazy((value) => {
  if (value !== undefined && value !== "") {
    return yup.string().min(6).max(64).lowercase();
  }
  return yup.string().nullable().optional();
}),

This method works for us.

Eleanore answered 25/4 at 1:40 Comment(1)
It's always a good idea to provide the version number you use! As code updates, what works for you may not work for others. Adding a specific version keeps developers from downvoting you unnecessarily.Solfatara

© 2022 - 2024 — McMap. All rights reserved.