Reputation: 2552
I'm using Material UI to create an autocomplete field with multiple inputs which allows the user to either select an existing email address, or enter in their own. For example, something like this:
Right now, the user can enter in their email addresses successfully, or select one from the dropdown menu - essentially, the same as the linked example above.
However, I am now trying to work on the email validation so that a couple of things happen:
As of now, I am able to validate the email address as in point 1 above, but I am not sure how to stop the value from being added to the list when the user hits the "enter" key. In addition, to remove the error message, I am only able to do so when the user types or removes additional characters (i.e. via the onChange
method). However, if the user interacts with the Autocomplete
component (for example, clicks "X" to remove the email address), the error stills shows.
This is what I have so far:
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: "[email protected]" },
{ title: "[email protected]" },
{ title: "[email protected]" }
];
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>
);
}
The example on Code Sandbox is here: https://codesandbox.io/s/tags-material-demo-forked-5l7ovu?file=/demo.tsx
Note: I'm not entirely sure how to provide the replicable code on Stack Overflow so I apologise in advance for linking my code to Code Sandbox instead.
Upvotes: 1
Views: 2491
Reputation: 22587
You need to use a controlled autocomplete. In onChange
we need to do -
If there is any invalid email, remove it from the array & update state to valid emails. (Chips)
We still need to show the invalid email as text (not a chip), for this we can set inputValue
. (Text)
Set or remove error.
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)));
}
As it controlled, we also need to handle chip's onDelete
& update state. Full code & working 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: "[email protected]" },
{ title: "[email protected]" },
{ title: "[email protected]" }
];
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>
);
}
Upvotes: 4