How to test for uniqueness of value in Yup.array?
I have form with dynamic amount of inputs (admin email) however checking for uniqueness fails:
validationSchema={Yup.object().shape({ adminEmails: Yup.array() .of( Yup.string() .notOneOf(Yup.ref('adminEmails'), 'E-mail is already used')
What is best approach here? FYI, as a form helper I use Formik
.
6 Answers
Try this:
Yup.addMethod(Yup.array, 'unique', function(message, mapper = a => a) { return this.test('unique', message, function(list) { return list.length === new Set(list.map(mapper)).size; }); });
Then use it like this:
const headersSchema = Yup.object().shape({ adminEmails: Yup.array().of( Yup.string() ) .unique('email must be unique') })
1This is a simple inline solution to validate that an array of strings only contains unique elements:
Yup.array().of(Yup.string()) .test( 'unique', 'Only unique values allowed.', (value) => value ? value.length === new Set(value)?.size : true )
1If you want to have the errors in each field and not in the array
Yup.addMethod(Yup.mixed, 'uniqueIn', function (array = [], message) { return this.test('uniqueIn', message, function (value) { return array.filter(item => item === value).length < 2; }); });
1Simply Do This It works for me
First Define this function in your react component
Yup.addMethod(Yup.array, "unique", function (message, mapper = (a) => a) { return this.test("unique", message, function (list) { return list.length === new Set(list.map(mapper)).size }) })
Just Put this schema inside your Formik tag
<Formik initialValues={{ hotelName: "", hotelEmail: [""], }} validationSchema={Yup.object().shape({ hotelName: Yup.string().required("Please enter hotel name"), hotelEmail: Yup.array() .of( Yup.object().shape({ email: Yup.string() .email("Invalid email") .required("Please enter email"), }), ) .unique("duplicate email", (a) => a.email), })} onSubmit={(values, { validate }) => { getFormPostData(values) }} render={({ values, errors, touched }) => ( <Form> <FieldArray name="hotelEmail" render={(arrayHelpers) => ( <> {values.hotelEmail.map((hotel, index) => ( <div class="row" key={index}> <div className="col-md-8 mt-3"> <div className="user-title-info user-details"> <div className="form-group d-flex align-items-center mb-md-4 mb-3"> <label className="mb-0" htmlFor="hotelName"> {lang("Hotelmanagement.hotelsystemadmin")} <sup className="text-danger">*</sup> </label> <div className="w-100"> <Field name={`hotelEmail.${index}.email`} className="form-control" id="hotelEmail" placeholder={lang( "Hotelmanagement.hotelsystemadmin", )} /> <span className="text-danger d-block"> {errors && errors.hotelEmail && errors.hotelEmail[index] && errors.hotelEmail[index].email && ( <span className="text-danger d-block"> {errors.hotelEmail[index].email} </span> )} {errors && errors.hotelEmail &&( <span className="text-danger d-block"> {errors.hotelEmail} </span> )} </span> </div> </div> </div> </div> <div className="col-md-2 mt-3"> {index > 0 && ( <i className="bx bx-minus addnewBtn " onClick={() => arrayHelpers.remove(index)} /> )} {index === values.hotelEmail.length - 1 && ( <i className="bx bx-plus addnewBtn ml-5" onClick={() => arrayHelpers.push("")} /> )} </div> </div> ))} </> )} />
not to show error do this following
{errors && errors.hotelEmail &&( <span className="text-danger d-block"> {errors.hotelEmail} </span> )}
)} /> Probably too late to respond, but anyways, you should use this.createError({path, message});
See example:
yup.addMethod(yup.array, 'growing', function(message) { return this.test('growing', message, function(values) { const len = values.length; for (let i = 0; i < len; i++) { if (i === 0) continue; if (values[i - 1].intervalTime > values[i].intervalTime) return this.createError({ path: `intervals[${i}].intervalTime`, message: 'Should be greater than previous interval', }); } return true; }); });
1To improve the performance of Alex's answer, use something like this to pre-calculate the lookup (using lodash).
yup.addMethod(yup.mixed, 'uniqueIn', function (array = [], message) { return this.test('uniqueIn', message, function (value) { const cacheKey = 'groups'; if (!this.options.context[cacheKey]) { this.options.context[cacheKey] = _.groupBy(array, x => x); } const groups = this.options.context[cacheKey]; return _.size(groups[value]) < 2; }); });
then call validate with an object for context so we can store our calculated group for the duration of the validate call
schema.validate(data, {context: {}}).then(...);
ncG1vNJzZmirpJawrLvVnqmfpJ%2Bse6S7zGiorp2jqbawutJobG1oaWiGcYWOoaawZaSkerWx0q1kn6eiYsKvtdCunKedo6h6sLKMr5ilrZVitq952K6nZpmip666