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 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" 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(...);

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like