Category Archives: Frontend

How to test for uniqueness of value in Yup.array?

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"
/>}

Putting HTML in inline JavaScript

A tag inside inline JavaScript string literal is interpreted by the HTML parser as a closing tag, causing syntax errors.
According to w3.org

Although the STYLE and SCRIPT elements use CDATA for their data model, for these elements, CDATA must be handled differently by user agents. Markup and entities must be treated as raw text and passed to the application as is. The first occurrence of the character sequence “</” (end-tag open delimiter) is treated as terminating the end of the element’s content. In valid documents, this would be the end tag for the element.

In real world scenario web browsers only end parsing a CDATA script block on an actual close-tag.
Unfortunately, there is no such special handling for script blocks in XHTML, causing < (or &) character to generate syntax errors if it is not properly escaped (i.e. HTML entity encoded).
There are different approaches to avoid such problems:

1) Encode the HTML string in base64 and decode it when reading
2) Split the <script> tag like that

var str = '</' + 'script' + '>';

3) Use CDATA

<script type="text/javascript">
//<![CDATA[
...code...
//]]>
</script>