RaveDash

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') }) 
1

This 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 ) 
1

If 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; }); }); 
1

Simply 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; }); });
1

To 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

Carisa Kissane

Update: 2024-05-21