Conditional Validation in Yup
Asked Answered
A

11

179

I have an email field that only gets shown if a checkbox is selected (boolean value is true). When the form get submitted, I only what this field to be required if the checkbox is checked (boolean is true).

This is what I've tried so far:

const validationSchema = yup.object().shape({
   email: yup
         .string()
         .email()
         .label('Email')
         .when('showEmail', {
             is: true,
             then: yup.string().required('Must enter email address'),
         }),
    })

I've tried several other variations, but I get errors from Formik and Yup:

Uncaught (in promise) TypeError: Cannot read property 'length' of undefined
    at yupToFormErrors (formik.es6.js:6198)
    at formik.es6.js:5933
    at <anonymous>
yupToFormErrors @ formik.es6.js:6198

And I get validation errors from Yup as well. What am I doing wrong?

Aurelioaurelius answered 20/3, 2018 at 21:27 Comment(1)
It's important to also set enableReinitialize to true along side the initialValues object.Aplenty
P
205

You probably aren't defining a validation rule for the showEmail field.

I've done a CodeSandox to test it out and as soon as I added:

showEmail: yup.boolean()

The form started validation correctly and no error was thrown.

This is the url: https://codesandbox.io/s/74z4px0k8q

And for future this was the correct validation schema:

validationSchema={yup.object().shape({
    showEmail: yup.boolean(),
    email: yup
      .string()
      .email()
      .when("showEmail", {
        is: true,
        then: yup.string().required("Must enter email address")
      })
  })
}
Pleasure answered 26/3, 2018 at 14:50 Comment(7)
How would this work with three possible values for showEmail? I.e. rather than a boolean() it was a string()Extravagant
You'll add a specific test to the is key using a function: is: (emailValue) => emailValue === "some test string you want it to match"Grained
Thanks for the answer, I really have no idea how you came up with this solution but that reflects a major problem with many npm packages, i.e. 90 percent of the library documentation should be pulled from the Git issues or SO answers.V2
Updated codesandbox codesandbox.io/s/formik-conditional-val-1ldmxSibylle
keep in mind, that there is "otherwise" key that is opposite to "then", where you can keep alternative validationsSuggestion
In a new yup v1 and upper version you have to return schema when doing conditiona like so then: (schema) => schema.min(5)...Southland
For this to now work you need to have then as a callback function.Stomacher
A
84

Formik author here...

To make Yup.when work properly, you would have to add showEmail to initialValues and to your Yup schema shape.

In general, when using validationSchema, it is best practices to ensure that all of your form's fields have initial values so that Yup can see them immediately.

The result would look like:

<Formik 
  initialValues={{ email: '', showEmail: false }}
  validationSchema={Yup.object().shape({
    showEmail: Yup.boolean(),
    email: Yup
      .string()
      .email()
      .when("showEmail", {
        is: true,
        then: Yup.string().required("Must enter email address")
      })
  })
}

/>
Antoineantoinetta answered 25/8, 2020 at 17:44 Comment(2)
"best practice to ensure that all of you form's fields have initial values..." A pretty common example where this doesn't hold true is number inputs. Say that you want your input to start empty. Not with zero, not with any other number. You would have to initialize this field to undefined.Eleonoreeleoptene
What do you do when you have nested form with object like notation? So basically a field which name ends up being e.g. "tax[0].country"Abundance
G
44

You can even use a function for complex cases . Function case helps for complex validations

validationSchema={yup.object().shape({
    showEmail: yup.boolean(),
    email: yup
      .string()
      .email()
      .when("showEmail", (showEmail, schema) => {
        if(showEmail)
          return schema.required("Must enter email address")
        return schema
      })
  })
}
Geometrize answered 6/1, 2021 at 5:51 Comment(1)
This works for me, without typescript complaining overloading methods. Thanks.Fund
E
25

Totally agree with @João Cunha's answer. Just a supplement for the use case of Radio button.

When we use radio button as condition, we can check value of string instead of boolean. e.g. is: 'Phone'

const ValidationSchema = Yup.object().shape({
  // This is the radio button.
  preferredContact: Yup.string()
    .required('Preferred contact is required.'),
  // This is the input field.
  contactPhone: Yup.string()
    .when('preferredContact', {
      is: 'Phone',
      then: Yup.string()
        .required('Phone number is required.'),
    }),
  // This is another input field.
  contactEmail: Yup.string()
    .when('preferredContact', {
      is: 'Email',
      then: Yup.string()
        .email('Please use a valid email address.')
        .required('Email address is required.'),
    }),

});

This the radio button written in ReactJS, onChange method is the key to trigger the condition checking.

<label>
  <input
    name="preferredContact" type="radio" value="Email"
    checked={this.state.preferredContact == 'Email'}
    onChange={() => this.handleRadioButtonChange('Email', setFieldValue)}
  />
  Email
</label>
<label>
  <input
    name="preferredContact" type="radio" value="Phone"
    checked={this.state.preferredContact == 'Phone'}
    onChange={() => this.handleRadioButtonChange('Phone', setFieldValue)}
  />
  Phone
</label>

And here's the callback function when radio button get changed. if we are using Formik, setFieldValue is the way to go.

handleRadioButtonChange(value, setFieldValue) {
  this.setState({'preferredContact': value});
  setFieldValue('preferredContact', value);
}
Evoy answered 3/7, 2019 at 0:45 Comment(1)
You saved a lot of timeGalateah
D
19
email: Yup.string()
    .when(['showEmail', 'anotherField'], {
        is: (showEmail, anotherField) => {
            return (showEmail && anotherField);
        },
        then: Yup.string().required('Must enter email address')
    }),
Deplete answered 23/4, 2021 at 14:48 Comment(0)
S
11

Attention anyone using Yup v1 and upper. v1.2 in my case. According to the official docs you have to do (schema) => ... in you conditions

Official Docs:
For schema with dynamic components (references, lazy, or conditions), describe requires more context to accurately return the schema description. In these cases provide options

import { ref, object, string, boolean } from 'yup';

let schema = object({
  isBig: boolean(),
  count: number().when('isBig', {
    is: true,
    then: (schema) => schema.min(5),
    otherwise: (schema) => schema.min(0),
  }),
});

schema.describe({ value: { isBig: true } });
Southland answered 2/6, 2023 at 13:0 Comment(0)
L
5

for typescript using this

reasonContractNotDone: yup.string().when('isContractDone', {
  is: false,
  then(schema) {
    return schema.required('Must enter email address');
  },
}),

NOT like below!

reasonContractNotDone: yup.string().when('isContractDone', {
  is: false,
  then: yup.string().required("Must enter email address")
}),
Limon answered 4/9, 2023 at 17:49 Comment(0)
P
3

it works for me very well :

   Yup.object().shape({
    voyageStartDate:Yup.date(),
    voyageEndDate:Yup.date()
        .when(
            'voyageStartDate',
            (voyageStartDate, schema) => (moment(voyageStartDate).isValid() ? schema.min(voyageStartDate) : schema),
        ),
})
Postliminy answered 27/12, 2021 at 6:41 Comment(1)
This is so close to what I'm looking for. If you want to make sure END > START, how would you access the end value in the .when here? I only want to compare if start is set.Stoker
A
2

Checking for a specific value without using the function notation:

If select choice has value date, then input field date is required:

availableDate: yup.string().when('choice', {
    is: (v) => v === 'date',
    then: (schema) => schema.required('date is required')
})
Attending answered 1/4, 2023 at 5:54 Comment(0)
O
1

I use yup with vee-validate

vee-validate

here is the sample code from project

const schema = yup.object({
    first_name: yup.string().required().max(45).label('Name'),
    last_name: yup.string().required().max(45).label('Last name'),
    email: yup.string().email().required().max(255).label('Email'),
    self_user: yup.boolean(),
    company_id: yup.number()
        .when('self_user', {
            is: false,
            then: yup.number().required()
        })
})
const { validate, resetForm } = useForm({
    validationSchema: schema,
    initialValues: {
        self_user: true
    }
})

const {
    value: self_user
} = useField('self_user')
const handleSelfUserChange = () => {
    self_user.value = !self_user.value
}
Octopod answered 17/9, 2021 at 7:38 Comment(0)
I
0
This Code Works For ME Try To use It

const validation = () => {
   try {
    let userSchema = Yup.object().shape({
    date: Yup.string().required(),
    city: Yup.string().required(),
    gender: Yup.string().required(),
    email: Yup.string()
      .matches(
        /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|. 
   (".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA- 
   Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
      )
      .nullable(true),
    userName: Yup.string().min(3),
  });
  userSchema.validateSync({
    email: email,
    userName: userName,
    gender: gender.name,
    city: city.name,
    date: date,
   });
 } catch (error) {
  console.log(error);
   setError({
     userName: '',
     email: '',
     gender: '',
     city: '',
     date: '',
     [error.path]: error.name,
    });
   }
 };
Imena answered 31/3, 2023 at 5:36 Comment(1)
Welcome to SO! Please refer to how do I write a good answer for some pointers on how to improve this answer. For example, it could use some explanation of your code and why it works/why it resolves the question. After all, we are not a code-writing service, but a place to learn.Lutetium

© 2022 - 2024 — McMap. All rights reserved.