I have a react app that I am currently updating which involves switching to react 16.8 and updating all libraries. I have two drop down selects from material UI, and due to this new version the multi select one no longer allows multiple options to be selected and I can't work out why. Any ideas would be appreciated!
Code:
import React from 'react';
import MenuItem from '#material-ui/core/MenuItem';
import FormControl from '#material-ui/core/FormControl';
import Select from '#material-ui/core/Select';
const names = [
'Oliver Hansen',
'Van Henry',
'Kelly Snyder',
];
export default function MultipleSelect() {
const [personName, setPersonName] = React.useState([]);
const handleChange = event => {
console.log(event) //holds the selected option correctly
setPersonName(event.target.value);
console.log(personName)
};
return (
<div>
<FormControl className={classname}>
<Select
multiple //used to be isMulti but this also no longer works
value={personName}
onChange={handleChange}
placeholder = {"choose a name"}
options={names}
>
</Select>
</FormControl>
</div>
);
}
This is because your value always contains a single string value. When you select a second items, it overrides the first value with new one. You need to assign an array of values to value prop with selected values. Push selected item in previously selected values array and update the state and on removal, remove that name from that array.
export default function MultipleSelect() {
const [selectedNames, setSelectedNames] = React.useState([]);
const handleChange = event => {
console.log(event) //holds the selected option correctly
// if selection/addition
setSelectedNames([...selectedNames, event.target.value]);
// On removal,
// setSelectedNames(selectedNames.filter(name => name !== event.target.value));
};
return (
<div>
<FormControl className={classname}>
<Select
multiple //used to be isMulti but this also no longer works
value={selectedNames}
onChange={handleChange}
placeholder = {"choose a name"}
options={names}
>
</Select>
</FormControl>
</div>
);
}
Set true value to multiple attribute:
<Select
multiple="true"
// ...
>
// ...
</Select>
Your options are currently single string. Select expects something like this (notice the value and label properties):
[
{value: 1, label: "Oliver Hansen"},
{value: 2, label: "Van Henry"},
{value: 3, label: "Kelly Snyder"}
]
If you have that in order your select should be working as expected.
Related
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;
import React from 'react';
import { makeStyles} from '#material-ui/core/styles';
import {Select, MenuItem} from '#material-ui/core';
import useState from 'react';
const test = () => {
const data = [
{TITLE : "Festival", PRIORITY : 3, STEP : 1},
{TITLE : "Headphone", PRIORITY : 2, STEP : 2},
{TITLE : "Mountain", PRIORITY : 1, STEP : 1}
]
return (
<>
{
data.map((info) => (
<div>
<div>{info.TITLE}</div>
<Select value={info.PRIORITY}>
<MenuItem value={1}> 1 </MenuItem>
<MenuItem value={2}> 2 </MenuItem>
<MenuItem value={3}> 3 </MenuItem>
</Select>
<Select value={info.STEP}>
<MenuItem value={1}> 1 </MenuItem>
<MenuItem value={2}> 2 </MenuItem>
<MenuItem value={3}> 3 </MenuItem>
</Select>
</div>
))
}
</>
)}
export default test;
In this code, I'm trying to control PRIORITY value and STEP value respectively.
I'm having trouble because, In my Data array, I have three items. Therefore, If I add
const [priority, setPriority] = useState(undefined);
const [step, setStep] = useState(undefined);
const priorityChange = (e) => {
setPriority(e.target.value)
};
const stepChange = (e) => {
setStep(e.target.value)
};
and put this value in
<Select value={priority} onChange={priorityChange}></Select>
...
<Select value={step} onChange={stepChange}></Select>
...
this item,
Every item gets the same value, therefore I'm unable to control each PRIORITY and STEP value.
How can I control each item? I need some help.
I might misspell. Please be understandable!
Firstly your data array is not hooked to any state managing variable. So when you try showing values initially from the data array and then try showing the updated data from the hooks variable when an action is trigged, it is obvious to cause some clash and hence fail to show the updated values. One way to get around this would be, to associate the initial data with a state hook and then update the data array accordingly. It is also important that correct data is updated that corresponds to the action triggered, so here we'd want to make sure that each object of the collection is unique, this could be accomplished by assigning an id attribute on each object. Further up, we can find out the object on which the action was taken on, mutate the property value and then re-construct the array using the state hook function to re-render with the correct updated value(s).
Kindly refer to the below code and read the comments to get a clearer idea.
import React, { useState } from "react";
const App = () => {
const [data, setData] = useState([
{ id: Math.random(), TITLE: "Festival", PRIORITY: 3, STEP: 1 },
{ id: Math.random(), TITLE: "Headphone", PRIORITY: 2, STEP: 2 },
{ id: Math.random(), TITLE: "Mountain", PRIORITY: 1, STEP: 1 }
]); //id attribute is added to each object to make sure every object in the array is unique.
const priorityChange = (e, id) => {
//This function should accept event and id arguments, to identify and update
//values correctly.
const index = data.findIndex((item) => item.id === id); //find the index of the object (item) whose priority needs to be updated.
const arr = [...data]; //Copy original array data to constant arr.
arr[index].PRIORITY = e.target.value; //mutate the PRIORITY property's value
setData([...arr]); //Set data to the new array with updated value.
};
const valueChange = (e,id) => {
//This function should accept event and id arguments, to identify and update
//values correctly.
const index = data.findIndex((item) => item.id === id); //find the index of the object (item) whose priority needs to be updated.
const arr = [...data];
arr[index].STEP = e.target.value; //mutate the STEP property's value
setData([...arr]); //Set data to the new array with updated value.
};
return (
<>
{data.map((info) => (
<div key={Math.random()}>
<div>{info.TITLE}</div>
<select
value={info.PRIORITY}
onChange={(e) => {
priorityChange(e, info.id); //pass event object and id corresponding to each array object.
}}
>
<option value={1}> 1 </option>
<option value={2}> 2 </option>
<option value={3}> 3 </option>
</select>
<select
value={info.STEP}
onChange={(e) => {
valueChange(e, info.id); //pass event object and id corresponding to each array object.
}}
>
<option value={1}> 1 </option>
<option value={2}> 2 </option>
<option value={3}> 3 </option>
</select>
</div>
))}
</>
);
};
export default App;
For example, if I have the functional component Dropdown that receives data from an api to populate a dropdown selection, and uses the following hook to update the state of the value selected
const [value, setValue] = React.useState();
How might I then access/read the value (in this case an array of numbers) outside of the Dropdown component?
For better context I will include firstly where the Dropdown component is used:
import React from 'react';
import Dropdown from '../Components/Dropdown.js'
import GraphTypeDropdown from '../Components/GraphTypeDropdown.js'
function CreateGraph() {
//function receiving and returning data (array of data points and their heading) from api
async function getData() {
const response = await fetch('/dropdowndata');
const receivedData = await response.json();
return receivedData;
}
//assigning data to myData variable
const myData = getData();
//myData is passed to Dropdown components as a prop to allow selection of data columns
//GraphTypeDropdown allows selection of the graph "type"
return (
<div>
<Dropdown myData={myData} />
<Dropdown myData={myData} />
<Dropdown myData={myData} />
<Dropdown myData={myData} />
<Dropdown myData={myData} />
<GraphTypeDropdown />
</div>
)
}
export default CreateGraph;
And also the full code of the dropdown functional component
import React from 'react';
import './Dropdown.css';
function Dropdown(props) {
const [columns, setColumns] = React.useState([]);
const [value, setValue] = React.useState();
//maps the prop myData to be the label and value of the dropdown list
React.useEffect(() => {
async function getColumns() {
props.myData.then(function(result) {
setColumns(result.map(({ heading, values }) => ({ label: heading, value: values })));
});
}
getColumns();
}, [props.myData]);
//creates a dropdown where value (array of data points) can be selected with label (column headings)
//value selected is then saved in the state of the dropdown
return (
<select className='DropDown'
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
>
{columns.map(({ label, heading, value }) => (
<option className='DropDown'
key={heading}
value={value}
>
{label}
</option>
))}
</select>
);
}
export default Dropdown;
How will I be able to use/access the arrays assigned to value in the five Dropdown components?
The answer to your question "access the value outside of the component?" would be:
You generally can not access the state of a functional component from outside that component.
What you can do is:
pass props "down" (to children)
props can be callback functions, this way you could pass state "up"
use a framework with global state, like redux.
Now it depends a lot on your use case what the best solution is.
An example for passing the state "up" would be:
function Dropdown(props) {
// ...
React.useEffect(() => {
props.onLoaded( columns );
}, [ columns ]);
// ...
}
function CreateGraph() {
// ...
<Dropdown
myData={ myData }
onLoaded={
(columns) => {
console.log('columns:', columns);
}
}
/>
// ...
}
I have a multiple select. Instead of using the default behaviour I am showing the total selected items in the input when there are any values selected.
The problem is that I want also to be able to type for search when some items are selected. I have managed to do that by returning the last children item from CustomValueContainer . But it's still not enough.
What I would like to do is to show the total items when the input has no focus, and hide it when the input is focused. So I need to get the reference to the input value from my CustomValueContainer in order to see if it's focused or not. Any ideas?
This is my code https://stackblitz.com/edit/react-hv89pn:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Select, { components as selectComponents, ValueContainerProps } from 'react-select';
import './style.css';
const CustomValueContainer = (props) => {
const length = props.getValue().length;
const children = props.children;
return length ? (
<selectComponents.ValueContainer {...props}>
<>
{length} selected
{children.slice(-1)}
</>
</selectComponents.ValueContainer>
) : (
<selectComponents.ValueContainer {...props}>{children}</selectComponents.ValueContainer>
);
};
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
render() {
return (
<Select
options = {[
{value: "1", label: "one"},
{value: "2", label: "two"},
{value: "3", label: "three"},
{value: "4", label: "four"},
]}
value={this.state.value}
onChange={value => this.setState({ value })}
isMulti
components={{ValueContainer: CustomValueContainer}}
clearable={true}
closeMenuOnSelect={false}
openMenuOnFocus={true}
/>
);
}
}
render(<App />, document.getElementById('root'));
Note that CustomValueContainer needs to be declared outside the render method (it does not work otherwise), but it's possible to pass a paremeter to it from render, I just need to pass it as a property of React Select and retrieve it from CustomValueContainer using props.selectProps.
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>
);
}
}