在自动完成字段中验证电子邮件地址

Validate email address within autocomplete field

我正在使用 Material UI 创建一个具有多个输入的自动完成字段,允许用户 select 现有的电子邮件地址,或输入他们自己的。例如,像这样:

现在,用户可以成功输入他们的电子邮件地址,或 select 从下拉菜单中输入一个 - 本质上与上面的链接示例相同。

但是,我现在正在尝试进行电子邮件验证,以便发生一些事情:

  1. 按下“回车”键后,我检查电子邮件是否有效。如果不是,则应向用户显示一条错误消息 并且 输入的电子邮件地址未添加到 运行 列表
  2. 只要出现错误,任何后续操作(退格键、键入、单击“X”等)都应删除错误消息

截至目前,我能够按照上面的第 1 点验证电子邮件地址,但我不确定如何在用户点击“输入”键时阻止将值添加到列表中。此外,要删除错误消息,我只能在用户键入或删除其他字符时这样做(即通过 onChange 方法)。但是,如果用户与 Autocomplete 组件进行交互(例如,单击“X”以删除电子邮件地址),错误仍然显示。

这是我目前拥有的:

import React, { useState } from "react";
import Chip from "@mui/material/Chip";
import Autocomplete from "@mui/material/Autocomplete";
import TextField from "@mui/material/TextField";
import Stack from "@mui/material/Stack";

export default function Tags() {
  const [emails, setEmails] = useState([]);
  const [currValue, setCurrValue] = useState(undefined);
  const regex = /^(([^<>()\[\]\.,;:\s@"]+(\.[^<>()\[\]\.,;:\s@"]+)*)|(".+"))@((([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  const [error, setError] = useState(false);

  const emailAddresses = [
    { title: "test_email_1@gmail.com" },
    { title: "test_email_2@gmail.com" },
    { title: "test_email_3@gmail.com" }
  ];

  const handleValidation = (e) => {
    // check if the user has hit the "enter" key (which is code "13")
    if (e.keyCode === 13 && !regex.test(e.target.value)) {
      setError(true);
    }
  };

  const handleChange = (e) => {
    // anytime the user makes a modification, remove any errors
    setError(false);
    setCurrValue(e.target.value);
  };

  console.log("emails", emails);

  return (
    <Stack spacing={3} sx={{ width: 500 }}>
      <Autocomplete
        multiple
        onChange={(event, value) => setEmails(value)}
        id="tags-filled"
        options={emailAddresses.map((option) => option.title)}
        freeSolo
        renderTags={(value: readonly string[], getTagProps) =>
          value.map((option: string, index: number) => (
            <Chip
              variant="outlined"
              label={option}
              {...getTagProps({ index })}
            />
          ))
        }
        renderInput={(params) => (
          <TextField
            {...params}
            variant="filled"
            label="Email Addresses"
            placeholder="Favorites"
            type="email"
            value={currValue}
            onChange={handleChange}
            onKeyDown={handleValidation}
            error={error}
            helperText={error && "Please enter a valid email address"}
          />
        )}
      />
    </Stack>
  );
}

Code Sandbox 的示例在这里:https://codesandbox.io/s/tags-material-demo-forked-5l7ovu?file=/demo.tsx

注意:我不完全确定如何在 Stack Overflow 上提供可复制的代码,因此对于将我的代码链接到 Code Sandbox 提前表示歉意。

您需要使用 controlled autocomplete。在 onChange 中我们需要做 -

  1. 如果有任何无效的电子邮件,将其从数组中删除并将状态更新为有效的电子邮件。 (筹码)

  2. 我们仍然需要将无效的电子邮件显示为文本(不是芯片),为此我们可以设置 inputValue。 (正文)

  3. 设置或删除错误。

function onChange(e, value) {
    // error
    const errorEmail = value.find((email) => !regex.test(email));
    if (errorEmail) {
      // set value displayed in the textbox
      setInputValue(errorEmail);
      setError(true);
    } else {
      setError(false);
    }
    // Update state, only valid emails
    setSelected(value.filter((email) => regex.test(email)));
  }

由于它的控制,我们还需要处理芯片的onDelete&更新状态。完整代码和工作 codesandbox

export default function Tags() {
  const [selected, setSelected] = useState([]);
  const [inputValue, setInputValue] = useState("");
  const regex = /^(([^<>()\[\]\.,;:\s@"]+(\.[^<>()\[\]\.,;:\s@"]+)*)|(".+"))@((([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  const [error, setError] = useState(false);

  const emailAddresses = [
    { title: "a@gmail.com" },
    { title: "b@gmail.com" },
    { title: "c@gmail.com" }
  ];

  function onChange(e, value) {
    // error
    const errorEmail = value.find((email) => !regex.test(email));
    if (errorEmail) {
      // set value displayed in the textbox
      setInputValue(errorEmail);
      setError(true);
    } else {
      setError(false);
    }
    // Update state
    setSelected(value.filter((email) => regex.test(email)));
  }

  function onDelete(value) {
    setSelected(selected.filter((e) => e !== value));
  }

  function onInputChange(e, newValue) {
    setInputValue(newValue);
  }

  return (
    <Stack spacing={3} sx={{ width: 500 }}>
      <Autocomplete
        multiple
        onChange={onChange}
        id="tags-filled"
        value={selected}
        inputValue={inputValue}
        onInputChange={onInputChange}
        options={emailAddresses.map((option) => option.title)}
        freeSolo
        renderTags={(value: readonly string[], getTagProps) =>
          value.map((option: string, index: number) => (
            <Chip
              variant="outlined"
              label={option}
              {...getTagProps({ index })}
              onDelete={() => onDelete(option)} //delete
            />
          ))
        }
        renderInput={(params) => (
          <TextField
            ....
          />
        )}
      />
    </Stack>
  );
}