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

79 Views Asked by At

I have created a dynamic form with validation in React with Formik.

What I have achieved so far:

Current code allows users to selecting the number of tickets to purchase and then entering the name and email of the person each ticket is for, both fields are required and the email field must contain a valid email address.

The "Buy Tickets" button simply displays the form values in an alert popup if the form is valid, and the "Reset" button resets the form back to it's initial state including the removal of all ticket name & email fields.

Issue: I want to add uniqueness amongst my members value. Like, If user selected 2 from drop down than Form will ask for Member name and email 2 times, If in the first Member field, user enters "abc" and in the second number of member column, user enters "abc" than it should show error like 'Member value must be unique".

I have followed this Link from Github but unable to achieve this with my code, I guess I am missing some step here.

Code Link: https://codesandbox.io/p/sandbox/react-hooks-counter-demo-forked-glp2dw?file=%2Fsrc%2FApp.jsx%3A71%2C26

How can I achieve this with my existing code?

Edit 1 - Structure Change:

=> I have implemented this solution and it worked. But, Now I have an array of strings instead of array of objects, which is look like this.

Sample Object:

const initialValues = {
  numberOfTickets: "",
  tickets: [
    "Ticket 1",
    "Ticket 2",
    "Ticket 3",
    "Ticket 4",
    "Ticket 5",
    "Ticket 6",
    "Ticket 7",
    "Ticket 8",
    "Ticket 9",
    "Ticket 10",
  ],
};

=> What changes should I implement in the validation schema, the onChangeTickets function, and the Yup.addMethod to achieve the same goal? My objective is to ensure that each array element is both unique and required."

Updated code with Yup.addMethod Method: https://codesandbox.io/p/sandbox/react-hooks-counter-demo-forked-pztv4s?file=%2Fsrc%2FApp.jsx%3A56%2C9-56%2C16

1

There are 1 best solutions below

4
hfsaito On

So with that solution you need to run this code once in your application

  Yup.addMethod(Yup.object, "uniqueProperty", function (propertyName, message) {
    return this.test("unique", message, function (value) {
      if (!value || !value[propertyName]) {
        return true;
      }

      const { path } = this;
      const options = [...this.parent];
      const currentIndex = options.indexOf(value);

      const subOptions = options.slice(0, currentIndex);

      if (
        subOptions.some(
          (option) => option[propertyName] === value[propertyName]
        )
      ) {
        throw this.createError({
          path: `${path}.${propertyName}`,
          message,
        });
      }

      return true;
    });
  });

The solution will be like this

import React from "react";
import { Formik, Form, Field, FieldArray, ErrorMessage } from "formik";
import * as Yup from "yup";

let setupYupCallOnce = false;
function setupYup() {
  if (setupYupCallOnce) return;
  setupYupCallOnce = true;
  /* The code above */
}

function App() {
  /* ... */

  const validationSchema = Yup.object().shape({
    numberOfTickets: Yup.string().required("Number of tickets is required"),
    tickets: Yup.array().of(
      Yup.object()
        .shape({
          name: Yup.string().required("Name is required"),
          email: Yup.string()
            .email("Email is invalid")
            .required("Email is required"),
        })
        .uniqueProperty("name", "Repeated name")
        .uniqueProperty("email", "Repeated email")
    ),
  });

  /* ... */

  React.useEffect(() => {
    setupYup();
  }, []);

  return (
    /* ... */
  );
}

export { App };