如何使用 formik 和 yup 验证对数组字段进行验证
How to perform validation of a array field using formik and yup validation
我在 React JS 工作。我需要使用 Formik 和 Yup 对数组字段进行验证,如下所示。验证条件是输入时间不应等于或介于现有时间之间(已经输入的时间。例如:如果我输入 14.05 它应该显示错误,因为输入时间已经在 14.00 之间(开始) 和 03:00(结束))。我如何验证我认为概念清晰的领域。如果有任何疑问,请尽管询问。
// 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>
</>
))}
编辑
我建议使用 moment 库然后你可以使用下面的代码:
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>
);
}
这里是 codesandbox 供您试用
我在 React JS 工作。我需要使用 Formik 和 Yup 对数组字段进行验证,如下所示。验证条件是输入时间不应等于或介于现有时间之间(已经输入的时间。例如:如果我输入 14.05 它应该显示错误,因为输入时间已经在 14.00 之间(开始) 和 03:00(结束))。我如何验证我认为概念清晰的领域。如果有任何疑问,请尽管询问。
// 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>
</>
))}
编辑 我建议使用 moment 库然后你可以使用下面的代码:
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>
);
}
这里是 codesandbox 供您试用