在 React 中验证字符串数组

Validating array of strings in React

我有一个包含一系列电子邮件的表单。该数组没有大小限制。我需要检查数组中的每个元素是否为有效电子邮件。用户应该能够向数组添加新元素,并且应该至少有一封电子邮件,每个新元素都应该有一个有效的电子邮件,并且每个电子邮件都应该是唯一的。我希望只有在用户第一次提交表单后才能进行验证。验证电子邮件列表的正确方法应该是什么?

我正在使用 Ant Design 组件并将无效电子邮件的索引列表保留为 invalidArrayIndexes,以便我可以在每个无效行上显示错误。当我添加一个新元素时,我无法收到所需的消息(“请输入您的电子邮件!”),并且当我添加或删除新元素时,已验证的索引列表变得混乱。我不确定这是否是验证反应中的字符串列表的正确方法。这是我到目前为止所做的:

import { Button, Form, Input } from "antd";
import { useState } from "react";

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const isValidEmail = (str) => {
  return emailRegex.test(str);
};

const MyForm = () => {
  const [emails, setEmails] = useState([""]);
  const [invalidArrayIndexes, setInvalidArrayIndexes] = useState([]);
  const [firstSubmit, setFirstSubmit] = useState(false);

  const addEmail = () => {
    const updatedEmails = [...emails];
    updatedEmails.push("");
    setEmails(updatedEmails);
  };

  const removeEmail = (index) => {
    const updatedEmails = [...emails];
    updatedEmails.splice(index, 1);
    setEmails(updatedEmails);
  };

  const formSubmitted = () => {
    if (!firstSubmit) {
      setFirstSubmit(true);
    }
    const notValidEmails = emails.filter((email) => {
      return !isValidEmail(email);
    });
    const invalidEmailExist = notValidEmails.length > 0;
    if (!invalidEmailExist) {
      console.log("now submitting");
      console.log(emails);
    }
  };

  const valChanged = (e, index) => {
    const updatedEmails = [...emails];
    updatedEmails[index] = e.target.value;
    if (firstSubmit) {
      const isValid = isValidEmail(e.target.value);
      if (isValid) {
        if (invalidArrayIndexes.indexOf(index) > -1) {
          const updatedInvalidArrayIndexes = [...invalidArrayIndexes];
          updatedInvalidArrayIndexes.splice(
            updatedInvalidArrayIndexes.indexOf(index),
            1
          );
          setInvalidArrayIndexes(updatedInvalidArrayIndexes);
        }
      } else {
        if (invalidArrayIndexes.indexOf(index) < 0) {
          const updatedInvalidArrayIndexes = [...invalidArrayIndexes];
          updatedInvalidArrayIndexes.push(index);
          setInvalidArrayIndexes(updatedInvalidArrayIndexes);
        }
      }
    }
    setEmails(updatedEmails);
  };

  const emailList = emails.map((email, index) => {
    return (
      <Form.Item
        key={index}
        name="email"
        label="email"
        rules={[{ required: true, message: "Please enter your email!" }]}
        validateStatus={invalidArrayIndexes.includes(index) && "error"}
        help={invalidArrayIndexes.includes(index) ? "not a valid email" : " "}
      >
        <Input
          style={{ width: 300 }}
          placeholder="enter email"
          value={email}
          onChange={(e) => valChanged(e, index)}
        />
        <Button type="label" onClick={() => removeEmail(index)}>
          remove email
        </Button>
      </Form.Item>
    );
  });

  return (
    <div>
      {emailList}
      <Button type="label" onClick={addEmail}>
        add new email
      </Button>
      <div style={{ marginTop: 20 }}>
        <Button type="primary" onClick={formSubmitted}>
          send emails
        </Button>
      </div>
    </div>
  );
};

export default MyForm;

我推荐使用类似于 yup 的库。 使用 yup,您可以为表单定义架构,如下所示:

const RegisterUserValidationSchema = yup.object().shape({
    name: yup.string()
        .min(2, "No way,too short!")
        .max(200, "So long,are you serious?")
        .required("Required"),
    email: yup.string().email("Please enter an email").required("Required"),
    password: yup.string().required("Password is required"),
    confirmPassword: yup.string()
        .oneOf([yup.ref('password'), null], "Passwords must match"),
    phoneNumber: yup.number("Please enter a valid phone number"),
})

然后,您可以将您的值与架构进行比较

您看不到所需错误消息的原因是,当您的元素被 Form 组件包裹并在提交表单后,ant design 的验证规则起作用。但是,这不会解决您的问题,因为这种用法仅支持单个表单项,每个表单项都具有唯一的 name。 我建议您使用 React 表单验证库 react-validatable-form 来验证您的字符串列表,因为它很好地抽象了验证工作流和来自 DOM 元素绑定的验证结果。首先,您应该使用 ReactValidatableFormProvider 包装您的应用程序,例如:

import { ReactValidatableFormProvider } from "react-validatable-form";
import "antd/dist/antd.css";
import MyForm from "./MyForm";

export default function App() {
  return (
    <ReactValidatableFormProvider>
      <MyForm />
    </ReactValidatableFormProvider>
  );
}

然后你可以使用 useValidatableForm 钩子与规则集 listPath 像:

import { Button, Form, Input } from "antd";
import { useValidatableForm } from "react-validatable-form";
import get from "lodash.get";

const initialFormData = {
  emails: [""]
};
const rules = [
  { listPath: "emails", ruleSet: [{ rule: "required" }, { rule: "email" }] }
];

const MyForm = () => {
  const {
    isValid,
    validationError,
    formData,
    setPathValue,
    setFormIsSubmitted
  } = useValidatableForm({
    rules,
    initialFormData,
    hideBeforeSubmit: true
  });

  const addEmail = () => {
    const updatedEmails = get(formData, "emails");
    updatedEmails.push("");
    setPathValue("emails", updatedEmails);
  };

  const removeEmail = (index) => {
    const updatedEmails = get(formData, "emails");
    updatedEmails.splice(index, 1);
    setPathValue("emails", updatedEmails);
  };

  const formSubmitted = () => {
    setFormIsSubmitted();
    if (isValid) {
      console.log("now submitting");
      console.log(get(formData, "emails"));
    }
  };

  const emailList = get(formData, "emails").map((email, index) => {
    return (
      <Form.Item
        key={index}
        validateStatus={get(validationError, `emails{${index}}`) && "error"}
        help={get(validationError, `emails{${index}}`) || " "}
      >
        <Input
          style={{ width: 300 }}
          placeholder="enter email"
          value={get(formData, `emails[${index}]`)}
          onChange={(e) => setPathValue(`emails[${index}]`, e.target.value)}
        />
        <Button type="label" onClick={() => removeEmail(index)}>
          remove email
        </Button>
      </Form.Item>
    );
  });

  return (
    <div>
      {emailList}
      <Button type="label" onClick={addEmail}>
        add new email
      </Button>
      <div style={{ marginTop: 20 }}>
        <Button type="primary" onClick={formSubmitted}>
          send emails
        </Button>
      </div>
    </div>
  );
};

export default MyForm;

您可以查看 this sandbox 的实际工作示例。