Search code examples
javascriptreactjsformikyupprimereact

How to perform validation of a array field using formik and yup validation


I am working in React JS . I need to implement validation using Formik and Yup for an array field as show below. Validation condition is such a way that the input time should not be equal to or between the existing time(The time that had already entered . Eg: if I enter 14.05 it should show an error, because the input time is already between 14.00 (start) and 03.00 (end) ). How can I validate the fields I think concept is clear. If there any doubts please do ask. image

// API values
//home_delivery_monday_times: Array(3)
//0: {start: "09:00", end: "11:00"}
//1: {start: "14:00", end: "03:00 "}
//2: {start: "11:30", end: "13:00 "}
//length: 3
 <Formik
                            initialValues={formData}
                            enableReinitialize={true}
                            validationSchema={yup.object({
                                settings: yup.object({
                                    home_delivery_monday_times:
                                        yup.array().of(yup.object({ start: yup.string() })).test("is-valid", "The value shouldn't equal or between the existing", function (value) {
                                            return (
                                                // Validating conditions
                                            )
                                        })

                                })
                            })}
                            onSubmit={(values: any, { setSubmitting }) => {
                                console.log("values", values)
                            }}
                        >
                            {formik =>
.....}
 

     {formik?.values?.settings?.home_delivery_monday_times?.map((item: any, index: any) => (
                                                                                <>
                                                                                    {index != 0 &&
                                                                                        <div className="p-col-12 p-sm-2 p-md-2" />
                                                                                    }
    
                                                                                    <div className="p-col-5 p-sm-4 p-md-4">
                                                                                        <label>Start</label>
                                                                                        <Field as={Calendar} value={item?.start ? new Date(`01-01-2000 ${item?.start}:00`) : ''} onSelect={(e: any) => { formik?.setFieldValue(`settings.home_delivery_monday_times.${index}.start`, moment(e?.value).format("HH:mm")) }} name={`settings.home_delivery_monday_times.${index}.start`} readOnlyInput={true} timeOnly />
                                                                                        <div className='p-col-12'>
                                                                                            <ErrorMessage name={`settings.home_delivery_monday_times.${index}.start`} component={FormErrorMsg} />
                                                                                        </div>
                                                                                    </div>
    
                                                                                    <div className="p-col-5 p-sm-4 p-md-4">
                                                                                        <label>End</label> <br />
                                                                                        <Field as={Calendar} value={item?.end ? new Date(`01-01-2000 ${item?.end?.trim()}:00`) : ''} onSelect={(e: any) => { formik?.setFieldValue(`settings.home_delivery_monday_times.${index}.end`, moment(e?.value).format("HH:mm")) }} name={`settings.home_delivery_monday_times.${index}.end`} readOnlyInput={true} timeOnly />
                                                                                    </div>
                                                                                    <div className="p-col-1 p-sm-2 p-md-2">
                                                                                        {index != 0 &&
                                                                                            <FiXCircle name="name" color="red" size="20px" onClick={() => formik?.values?.settings?.home_delivery_monday_times?.splice(index, 1)} />
                                                                                        }
                                                                                    </div>
                                                                                </>
    
                                                                            ))}


Solution

  • EDIT I advice making use of the moment library then you can use the code below:

    import * as Yup from "yup";
    import { Formik, Form, Field, FieldArray, ErrorMessage } from "formik";
    import moment from "moment";
    
    export default function App() {
      const isValid = (timeSlots) => {
        if (!timeSlots) return;
    
        // compare each slot to every other slot
        for (let i = 0; i < timeSlots.length; i++) {
          const slot1 = timeSlots[i];
    
          if (!slot1.start || !slot1.end) continue;
    
          const start1 = moment(slot1.start, "HH:mm");
          const end1 = moment(slot1.end, "HH:mm");
    
          for (let j = 0; j < timeSlots.length; j++) {
            // prevent comparision of slot with itself
            if (i === j) continue;
    
            const slot2 = timeSlots[j];
    
            if (!slot2.start || !slot2.end) continue;
            const start2 = moment(slot2.start, "HH:mm");
            const end2 = moment(slot2.end, "HH:mm");
    
            if (
              start2.isBetween(start1, end1, undefined, "[]") ||
              end2.isBetween(start1, end1, undefined, "[]")
            ) {
              return `Overlapping time in slot ${j + 1}`;
            }
          }
        }
        // All time slots are are valid
        return "";
      };
    
      const handleSubmit = (values) => {
        console.log(values.mondayTimes);
      };
    
      return (
        <div className="container m-3">
          <Formik
            initialValues={{ mondayTimes: [{ start: "", end: "" }] }}
            onSubmit={handleSubmit}
            validationSchema={Yup.object().shape({
              mondayTimes: Yup.array()
                .of(
                  Yup.object().shape({
                    start: Yup.string().test("startTest", "Invalid Time", function (
                      value
                    ) {
                      if (!value) return true;
                      return moment(value, "HH:mm").isValid();
                    }),
                    end: Yup.string().test("endTest", "Invalid Time", function (
                      value
                    ) {
                      if (!value) return true;
                      if (!moment(value, "HH:mm").isValid()) {
                        return this.createError({ message: "Invalid Time" });
                      }
    
                      if (
                        moment(this.parent.start, "HH:mm").isSameOrAfter(
                          moment(this.parent.end, "HH:mm")
                        )
                      ) {
                        return this.createError({
                          message: "End time must be after start time"
                        });
                      }
    
                      return true;
                    })
                  })
                )
                .test("timesTest", "Error", function (value) {
                  const message = isValid(value);
                  return !message;
                })
            })}
            render={({ values, errors }) => (
              <Form>
                <FieldArray
                  name="mondayTimes"
                  render={(arrayHelpers) => (
                    <div className="">
                      {values.mondayTimes.map((time, index) => (
                        <div className="row" key={index}>
                          <div className="col-5">
                            <div className="mb-3">
                              <label htmlFor="" className="form-label">
                                Start
                              </label>
                              <Field
                                className="form-control"
                                name={`mondayTimes.${index}.start`}
                              />
                              <ErrorMessage
                                className="form-text text-danger"
                                name={`mondayTimes.${index}.start`}
                              />
                            </div>
                          </div>
                          <div className="col-5">
                            <div className="mb-3">
                              <label htmlFor="" className="form-label">
                                End
                              </label>
                              <Field
                                className="form-control"
                                name={`mondayTimes.${index}.end`}
                              />
                              <ErrorMessage
                                className="form-text text-danger"
                                name={`mondayTimes.${index}.end`}
                              />
                            </div>
                          </div>
    
                          <div className="col-2 mt-4">
                            <button
                              className="btn btn-sm btn-danger m-2"
                              type="button"
                              onClick={() => arrayHelpers.remove(index)}
                            >
                              -
                            </button>
                          </div>
                        </div>
                      ))}
    
                      {isValid(values.mondayTimes)}
    
                      <button
                        className="btn btn-sm btn-primary m-2"
                        type="button"
                        onClick={() =>
                          arrayHelpers.insert(values.mondayTimes.length, {
                            start: "",
                            end: ""
                          })
                        }
                      >
                        +
                      </button>
                      <div>
                        <button className="btn btn btn-primary m-2" type="submit">
                          Submit
                        </button>
                      </div>
                    </div>
                  )}
                />
              </Form>
            )}
          />
        </div>
      );
    }
    

    Here is the codesandbox for you to try out