reactjstypescriptvalidationformikyup

Yup validate either one of two fields is required (one of them is an array of numbers)


I'm using Formik with Yup and Typescript, and I have this as an initial value of the form ...

const initialValues = {
    title: "",
    overview: "",
    related_items_id: [],
    short_desc: ""
};

And here's my schema ...

const formSchema = Yup.object().shape({
    title: Yup.string()
        .trim()
        .required("This field is required."),
    overview: Yup.string().required("This field is required."),
    related_items_id: Yup.array()
        .min(1, "Pick at least 1 item")
        .of(Yup.number().required("This field is required.")),
    short_desc: Yup.string().required("This field is required.")
});

Now, I need either the related_items_id array or the short_desc to be required, if one is filled with data the other is not required and vise Versa, how can I accomplish that using something like when in yup?

Here's a codesandbox I created to show the error that I'm getting when trying to use the when method of Yup ...

https://codesandbox.io/s/formik-yup-required-one-or-the-other-nextjs-gujqv


Solution

  • For conditionally setting your formSchema, checkout the docs Conditionally Set Required Field (Yup).

    const basicFormSchema = Yup.object().shape(
        {
          title: Yup.string()
            .trim()
            .required("This field is required."),
          overview: Yup.string().required("This field is required."),
          related_items_id: Yup.array().when("short_desc", {
            is: "",
            then: Yup.array()
              .min(1, "Pick at least 1 item")
              .of(Yup.number().required("This field is required.")),
            otherwise: Yup.array()
          }),
          short_desc: Yup.string().when("related_items_id", {
            is: relatedItemsId => relatedItemsId.length === 0,
            then: Yup.string().required("This field is required."),
            otherwise: Yup.string()
          })
        },
        [["related_items_id", "short_desc"]]
      );
    

    You can achieve this by creating a type that is interdepedent on related_items_id and short_desc

    export interface BaseType {
        title: string;
        overview: string;
    }
    
    interface RelatedItemsType extends BaseType {
      related_items_id?: Array<any>;
      short_desc?: never;
    }
    
    interface ShortDescType extends BaseType {
      related_items_id?: never;
      short_desc?: string;
    }
    
    export type InitialValueType = RelatedItemsType | ShortDescType;
    

    and you can make use of it like this

    const initialValues: InitialValueType = {
        title: "",
        overview: "",
        related_items_id: [],
        // short_desc: "" no longer required
    };