21

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

Ruby
  • 2,207
  • 12
  • 42
  • 71

4 Answers4

16

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
};

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"]]
  );
ioedeveloper
  • 528
  • 3
  • 11
  • Thanks for the effort, but could you please provide an example of how the schema would look like, as this is the confusing part for me, not the Typescript part. – Ruby Jan 17 '20 at 09:34
  • I edited my answer with an example to how to go about it. @Ruby – ioedeveloper Jan 17 '20 at 10:40
  • 1
    Thanks my friend, I actually got this far before asking this question, it's just I always get this error ... Cyclic dependency, node was:"short_desc". I don't know why. When I tried to google it, I found people doing it this way, but that didn't help me ... https://github.com/jquense/yup/issues/176#issuecomment-369925782 – Ruby Jan 17 '20 at 11:02
  • I think you should pass a third param to `Yup.object().shape` wich is an array, but I can't figure out what is the shape of that array would be. I'll try to make a Codesandbox demonstrate this. – Ruby Jan 17 '20 at 11:05
  • Sure, create a sandbox and share it. – ioedeveloper Jan 17 '20 at 11:53
  • Here you go ... https://codesandbox.io/s/formik-yup-required-one-or-the-other-nextjs-gujqv – Ruby Jan 17 '20 at 14:08
  • @Ruby I've updated my answer with a working formSchema, you can check it out here https://codesandbox.io/s/formik-yup-required-one-or-the-other-nextjs-8yqbs – ioedeveloper Jan 18 '20 at 06:42
  • With this code none of them is required, you should get rid of the '?' for the value that is mandatory. interface RelatedItemsType extends BaseType { related_items_id: Array; short_desc?: never; } interface ShortDescType extends BaseType { related_items_id?: never; short_desc: string; } – Bri4n Jun 01 '20 at 16:20
13

This is how you check if at least one of them is completed.

const schema = yup.object().shape({
  'fieldOneName': Yup.string()
  .when('fieldTwoName', {
    is: (fieldTwo) => !fieldTwo || fieldTwo.length === 0,
    then: Yup.string()
      .required('At least one of the fields is required'),
  }),
  'fieldTwoName': Yup.string()
  .when(codiceFiscale.name, {
    is: (fieldOne) => !fieldOne || fieldOne.length === 0,
    then: Yup.string().
      .required('At least one of the fields is required'),,
  })
}, ['fieldOneName', 'fieldTwoName']) // <-- HERE!!!!!!!!
Artan M.
  • 817
  • 1
  • 11
  • 16
  • 3
    Your array `['fieldOneName', 'fieldTwoName']` should be wrapped in another array. This may be due to an update, I'm not sure. See docs: https://github.com/jquense/yup#objectshapefields-object-nosortedges-arraystring-string-schema Also, could you add an explanation for what that argument does? Thank you! – Fernando Rojo Jan 28 '22 at 15:55
8
<Formik
            initialValues={{
                email: '',
                mobile: '',
                submit: null,
            }}

            validationSchema = {                                                                                         
    Yup.object().shape({                                                                                 
            'email': Yup.string()                                                                        
                        .when('mobile', {                                                    
                        is: (mobile) => !mobile || mobile.length === 0,                      
                        then: Yup.string()                                                   
                        .required('At least one of the fields is required'),                 
                                    }),                                                                  
            'mobile': Yup.string()                                                                       
                        .when('email', {                                                     
                        is: (email) => !email || email.length === 0,                         
                        then: Yup.string()
                        .required('At least one of the fields is required')
                        })                                                                   
    }, ['email', 'mobile'])                                                                              
}                                                                                                        



            
Nani
  • 167
  • 4
  • 15
  • I tried this with ustid and taxnumber, but get an error. Error: Cyclic dependency, node was:"taxnumber" But i'm not shure what this does mean. – freesh May 24 '22 at 13:24
  • 1
    Ahh I was blind and forget to use .shape({...}, [['field', 'field2']]) I my sorry and will buying glasses now. – freesh May 24 '22 at 13:40
8

If using when isn't a strict requirement, I find this is a little easier to do using the test function as it's more concise, easier to follow, and doesn't require duplication of code or messages

const has = require('lodash.has'); // lodash's submodule

validationSchema = {                                                                                         
    Yup.object().shape({                                                                                 
            'email': Yup.string(),                                                                  
            'mobile': Yup.string(),
    })
    .test(
        'email or mobile',
        'email or mobile is required',
        (value) => has(value, 'email') || has(value, 'mobile')
    );
Hank Chan
  • 1,706
  • 1
  • 16
  • 21
ChrisJ
  • 2,486
  • 21
  • 40