React: How to create a select multiple input that accepting same value - javascript

I'm using antd library and reactJS. I want the user to be able to input multiple values into a select multiple input statement, and the user can input the same value at once. Something like: [20, 100, 100]. Currently in normal multiple or tags mode, when the user enters another input that already exists, the input will be removed. Basically, I wanted to keep it there. These are the codes I got from antd docs:
const children = [];
function handleChange(value) {
console.log(`selected ${value}`);
}
ReactDOM.render(
<Select mode="tags" style={{ width: '100%' }} placeholder="Tags Mode" onChange={handleChange}>
{children}
</Select>,
document.getElementById('container'),
);
I have tried:
Setting a unique key for each of the values selected. But I found difficulties in modifying the current options that the user selected. There is no API provided that I can use to update the current options.
Setting a unique key as the user selects the option by appending Date.now() to the key. But again, I'm not sure how to do this on the select props. Something like this:
ReactDOM.render(
<Select
mode="tags"
style={{ width: "100%" }}
placeholder="Tags Mode"
onChange={handleChange}
value={Date.now() + '' + selectedValue}
>
{children}
</Select>
But again, selectedValue is not a valid variable that I can use.
I tried adding labelInValue, but it just added the id and value, with no option to customize the id.

Note: This solution only fix the problem that when ever we have a tag in select and you try to add the same tag, it do not remove the existing selected tag from Antd Select.
import { Select } from "antd";
import { useState } from "react";
function App() {
const [value, setValue] = useState([]);
const [searchValue, setSearchValue] = useState("");
// const [options, setOptions] = useState([]);
const onSelect = (value) => {
setValue((prev) => [...prev, value]);
setSearchValue("");
// If you want to show tags after user added removes it, you can enable this code
// Enable options and setOptions
// setOptions((prev) => {
// const index = prev.find((o) => o.value === value);
// if (!index) {
// return [...prev, { label: value, value }];
// }
// return prev;
// });
};
const onDeselect = (value) => {
if (value !== searchValue) {
setValue((prev) => prev.filter((v) => v !== value));
}
setSearchValue("");
};
return (
<div className='App'>
<Select
// options={options}
mode='tags'
searchValue={searchValue}
onSearch={setSearchValue}
value={value}
style={{ width: "100%" }}
placeholder='Tags Mode'
onSelect={onSelect}
onDeselect={onDeselect}
/>
</div>
);
}
export default App;

Related

how to add dinamically components with data, on objects list with react js

I have a component list, in this case is a MUI chips, which has some props (label, callback) and I need to add them to my list when onClick event is triggered.
The chip is going to have a label, which is a name selected from a dropdown menu.
I found quite difficult to have a unique chip with the name selected.
//my components list
const [chipList, setChip] = useState([{ chip: "" }]);
const addNewCategory = () => {
if (chipList.length < 5) {
setChip([...chipList, { chip: "" }]);
}
};
//my map to render the component
{chipList.map((widget, index) => (
<CategoryChip key={index} label={subCategory} onDelete={() => handleDelete(index)} />
))}
I am quite sure I have to pass the label inside my useState([{ chip: "" }]) and yes I know, for the moment my chips has all same name because of the label attribute
You don't need to map() your chipList if your intent is to only show one. The one that is selected.
I'm assuming your subCategory state or prop is the chip info that you chose from the dropdown.
You can use findIndex() to show CategoryChip related with that choice.
export default YourComponent = () => {
const [chipList, setChip] = useState([{ chip: "" }]);
const addNewCategory = () => {
if (chipList.length < 5) {
setChip([...chipList, { chip: "" }]);
}
};
...
const renderSelectedChip = () => {
const foundChipIndex = chipList.findIndex(el => el.chip === subCategory);
// I checked for subCategory just to make sure that empty string wasn't a selectable option, but you can remove it if you want
if (!subCategory || foundChipIndex === -1) {
return <></>;
}
return <CategoryChip label={subCategory} onDelete={() => handleDelete(foundChipIndex)} />
))} />
}
return (
<>
...
{renderSelectedChip()}
</>
)
}

Material-ui Autocomplete - onChange not triggered when updating value in onHighlightChange

I've been working on an extended version of Material UI's Autocomplete where I am implementing a feature that allows the user to move the option to the input via keyboard events (Arrow key up + down). The user should then be allowed to select one of the options via the ENTER key.
For some reason, the onChange event is not triggered and I am kind of puzzled to understand why this happens.
export default function Autocompleter() {
const [input, setInput] = React.useState(null);
const handleInputChange = (event, option, reason) => {
console.log('On input change triggered');
};
const handleOnChange = (event, value, reason) => {
console.log('On change triggered! ');
};
const handleHighlightChange = (event, option, reason) => {
if (option && reason === 'keyboard') {
setInput(option);
}
};
const handleFilterOptions = (currentOptions) => currentOptions;
const handleGetOptionsLabel = (option) => {
return option.label;
};
return (
<Autocomplete
id="combo-box-demo"
freeSolo={true}
value={input}
onChange={handleOnChange}
onInputChange={handleInputChange}
options={top100Films}
isOptionEqualToValue={(option, value) => option.label === value.label}
includeInputInList={true}
onHighlightChange={handleHighlightChange}
getOptionLabel={handleGetOptionsLabel}
filterOptions={handleFilterOptions}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
);
}
Here is also a working example:
https://stackblitz.com/edit/react-ts-rsodyc?file=index.tsx,App.tsx,Autocompleter.tsx
NOTE: This is a light example of my original code, but it should be enough to address the issue.
There are a few things I tried such as using inputValue in combination with the onHighlightChange but this does not seem to work either.
includeInputInList seemed to be the solution according to the doc, but it does nothing? Does anyone understand what it is supposed to do and is it helpful in my case?
UPDATE:
Updating the input state in onHighlightChange breaks the onChange. Unfortunately, I do want to update the input every time the user highlights an option via keyboard events.
Thank you for any kind of help and idea
Since I found that to my knowledge its not possible to check for a "Enter" key on the handleHighlightChange function I've come up with this.
highlightedInput is a seperate state for the highlighted value, this way you can keep track of the currently highlighted input. We set this in the handleHighlightChange after our checks.
We want to change our input state when we click Enter, normally when clicking the Enter key the dropdown closes. To handle this we can create a state for the open state of the dropdown. For this we need a handleOpen and a custom close handler handleOnclose here we can set the currently highlighted value (highlightedInput) to the actual input state.
const [input, setInput] = React.useState(null);
const [isOpen, setIsOpen] = React.useState(false);
const [highlightedInput, setHighlightedInput] = React.useState(null);
const handleOpen = () => {
setIsOpen(true);
};
const handleOnClose = (event, option, reason, details) => {
if (option && event.key === "Enter") {
setInput(highlightedInput);
}
setIsOpen(false);
};
const handleInputChange = (event, option, reason) => {
console.log("On input change triggered");
};
const handleOnChange = (event, value, reason) => {
console.log("On change triggered!");
};
const handleHighlightChange = (event, option, reason) => {
if (option && reason === "keyboard") {
setHighlightedInput(option);
}
};
const handleFilterOptions = (currentOptions) => currentOptions;
const handleGetOptionsLabel = (option) => {
return option.label;
};
Note that we changed the value from the AutoComplete to the highlightedInput instead of input.
return (
<React.Fragment>
<Autocomplete
id="combo-box-demo"
freeSolo={true}
open={isOpen}
onOpen={handleOpen}
onClose={handleOnClose}
value={highlightedInput}
onChange={handleOnChange}
onInputChange={handleInputChange}
options={top100Films}
isOptionEqualToValue={(option, value) => option.label === value.label}
includeInputInList={true}
onHighlightChange={handleHighlightChange}
getOptionLabel={handleGetOptionsLabel}
filterOptions={handleFilterOptions}
style={{ width: 300 }}
renderInput={(params) => (
<TextField {...params} label="Combo box" variant="outlined" />
)}
/>
<div style={{ height: "200px" }}></div>
{input?.label}
</React.Fragment>
);
Live version
The onChange handler runs when the user selects an option from the drop down. Seams that you want the onInputChange event. That one fires when you type in the input field.

Validate email address within autocomplete field

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:
Upon hitting the "enter" key, I check whether the email is valid or not. If it's not, an error message should be displayed to the user and the entered email address is not added to the running list
Whenever there is an error, any subsequent action (either backspace, typing, click "X" etc), should remove the error message
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: "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>
);
}
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.
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: "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>
);
}

I need help implementing Autocomplete from antdesign ui

i'm still learning and having some trouble understanding the syntax of the [autocomplete][1] from ant design framework ( [1]: https://ant.design/components/auto-complete/)
I need the user to search some value, and then the component will show a suggestions list based on what objects i have
So i wrote the logic to that search,put some fixed value as a const to text and it works
but now i can't understand how to substitute the input value in my logic and how to show the options on the autocomplete component.
here's the call
const [proceds,setProceds] = useState([])
const text = 'aparelho'
useEffect(() => {
const loadProceds = async () => {
const res = await axios.get('myapi_url');
setProceds([...res.data])
}
loadProceds();
},[])
let results = proceds.filter((proced) =>
proced.terminologia.toLowerCase().includes(text.toLowerCase())
);
console.log(results);
in this case it searchs the text and shows which objcs in my api contains it .
my autocomplete blank template is like this
function handleChange(value) {
console.log(`selected ${value}`);
}
const Complete = () => (
<AutoComplete
style={{ width: 120 }}
dropdownMenuStyle={{ maxHeight: 1000, overflowY: 'visible' }}
onChange={handleChange}
>
</AutoComplete>
);
return (
<>
<Complete />
</>

How to add the input field inside the select option using ant design and react

I created select option using ant design .But I need create editable cell inside the select option.
This my select option code
<Select
showSearch
style={{ width: 400 }}
placeholder="Select a Bank"
optionFilterProp="children"
onChange={this.handleChange.bind(this)}
>
<option value="1">Bank1</option>
<option value="2"> Bank2</option>
<option value="3"> Bank3</option>
</Select>
And onChange functions is
handleChange(value) {
console.log(`selected ${value}`);
this.setState({
bank:value,
});
}
Can you help me?
I suppose the question is whether or not this is an editable list.
The Select component has a mode prop that can be used to change the functionality with the following options:
'default' | 'multiple' | 'tags' | 'combobox'
Using the tags mode would allow you to add and remove items and generate a tokenized list when the form is submitted.
If you are looking at a fixed list and then wanting to create new items to add to the list:
If you want to be able to add new items to the list, this doesn't exist currently, as far as I am aware.
You may be able to refashion something from the Ant Design Pro components, or otherwise come up with a solution where:
when "create" is selected, you toggle the Select for an Input
when the input is submitted/blurred update the Options list, toggle the Select/Input once more and submit the value to the back-end.
I hope this helps.
You don't need to do that actually. All you need to do is to use component state and two simple callback functions ant design provides for select.
So let's assume you need to allow users not to also search for existing values inside a Select but if it didn't exist they can choose a new one. So here's what I'd do:
Inside render() method:
<Select
showSearch
value={this.title}
filterOption={true}
onSearch={this.handleSearch}
onFocus={this.handleFocus}
style={{ width: "100%" }}>
{this.titles.map((title) => (
<Select.Option key={title}>{title}</Select.Option>
))}
</Select>
Where this.titles = ["something", "else"].
Then Inside this.handleSearchand this.handleFocus I'd write:
protected handleSearch = (value: string) => {
this.setState({ titles: value && value !== "" ? [...this.titles, value] : fileTitles });
};
protected handleFocus = () => {
this.setState({ this.titles });
};
What we're basically doing is to populate the options we're iterating over inside the Select with this.titles in the state of the component itself (don't confuse it with Redux or MobX) when user opens the selector and once user searches for anything that would be added to options as well. With this approach you won't need an input or a switch to show/hide inputs. Hope it helps.
You could use another modal to input the additional value.
Check this : https://codesandbox.io/s/antdselectaddoption-7fov7
Code from mamsoudi throws Errors, so i took his idea and made my own component that i'm sharing with you.
import React from 'react';
import {Select} from "antd";
class FieldSelectAndCustomText extends React.Component {
constructor(props) {
super(props);
this.initialTitles = ["something", "else"];
this.state = {
titles: this.initialTitles,
currentValue: null,
};
}
handleSearch = (value) => {
const titles = this.state.titles;
for (let i = 0; i < titles.length; i++) {
const isSearchValueInState = new RegExp(value).test(titles[i]);
if (!isSearchValueInState) {
this.setState({
titles: [...this.initialTitles, value],
currentValue: value
});
break;
}
}
};
handleChange = (value) => {
this.setState(prev => ({...prev, currentValue: value}));
}
render () {
return (
<div>
<Select
showSearch
value={this.state.currentValue}
filterOption={true}
onSearch={this.handleSearch}
onChange={this.handleChange}
onFocus={this.handleFocus}
style={{ width: "100%" }}>
{this.state.titles.map((title) => (
<Select.Option value={title} key={title}>{title}</Select.Option>
))}
</Select>
</div>
);
}
}

Categories

Resources