Imagine you have a form with a dynamic amount of inputs – in my case, it is an array of objects holding “name” and “surname” properties and you need to validate that all objects in this array have unique name
property.
Example schema:
const users = yup.array().of(yup.object().shape({
name: yup.string(),
surname: yup.string(),
}))
How can you test for uniqueness? There is no built-in validator, but you can create a custom one easily extending Yup.test. This validator allows you to add a test function to the validation chain. Tests are run after any object is cast. In order to allow asynchronous custom validations all (or no) tests are run asynchronously. A consequence of this is that test execution order cannot be guaranteed.
All tests must provide a name
, an error message
and a validation function that must return true
when the current value
is valid and false
or a ValidationError
otherwise. To make a test async return a promise that resolves true
or false
or a ValidationError
.
For the message
argument you can provide a string which will interpolate certain values if specified using the ${param}
syntax. By default, all test messages are passed a path
value which is valuable in nested schemas.
The test
function is called with the current value
.
const users = yup.array().of(yup.object().shape({
name: yup.string(),
surname: yup.string(),
})).test(
'unique',
t('step2.validation_errors.duplicate').toString(),
(value) => {
if (!value) return true;
const unique = value.filter((v: any, i: number, a: any) => a.findIndex((t: any) => (t.name === v.name && t.surname === v.surname)) === i);
return unique.length === value.length;
}
)
Because the uniqueness check is not related to a particular element of the users object but is a general validation constraint for the whole object, the error message will be populated as “errors.people” property of Formik. That’s why I am checking if the erros.users is a string – if it is a string, I should display a general error above all the user’s fields, but if it is an array of errors – it contains errors for a particular index of the users array.
{typeof errors.users === 'string' && <ErrorMessage
name="users"
component="div"
className="elementor-field-group mt-2 text-sm text-red-600 dark:text-red-500"
/>}