I need a simple "required" validation for 'react-select' (github repo).
In the latest version it uses css-in-js approach. So I have custom styles:
export const customStyles = {
control: (base, state) => ({
...base,
}),
menu: (base, state) => ({
...base,
}),
menuList: (base, state) => ({
...base,
}),
}
How can I change e.g. borderColor if field is invalid?
On this point there's an issue opened on GitHub.
I see two different approaches:
the "lazy" one, where you change the border colour by adding a specific className. Example here.
As you want to custom the original select I would recommend to embed your customSelect in a separate file. Then you can pass a props isValid and use it to change your borderColor.
class CustomSelect extends React.Component {
render() {
const {
isValid
} = this.props
const customStyles = {
control: (base, state) => ({
...base,
// state.isFocused can display different borderColor if you need it
borderColor: state.isFocused ?
'#ddd' : isValid ?
'#ddd' : 'red',
// overwrittes hover style
'&:hover': {
borderColor: state.isFocused ?
'#ddd' : isValid ?
'#ddd' : 'red'
}
})
}
return <Select styles={ customStyles } {...this.props}/>
}
}
A help for someone who doesn't want to add all time some codes for only of this required validation in react-select. Just use react-hook-form-input.
<RHFInput
as={<Select options={options} />}
rules={{ required: 'Please select an option'}}
name="reactSelect"
register={register}
setValue={setValue}
/>
Where this RHFInput from react-hook-form-input was just a save for me.. Complete example- react-hook-form-input.
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
];
function App() {
const { handleSubmit, register, setValue, reset, errors } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: 'Please select an option'}}
name="reactSelect"
register={register}
setValue={setValue}
/>
<span className="text-danger">
{errors.reactSelect && errors.reactSelect.type === 'required' && "Please select"}
</span>
<button type="button">Reset Form</button>
<button>submit</button>
</form>
);
}
Hope, it will help someone like me as a beginner in react.
render.js
export const renderSelect = (props) => (
<div>
<Select
{...props}
value={props.input.value}
onChange={(value) => props.input.onChange(value)}
onBlur={() => props.input.onBlur(props.input.value)}
options={props.options}
key={props.input.value}
/>
{props.meta.touched && (props.meta.error && <p style={{ color: "red",fontSize:"12px" }}>{props.meta.error}</p>)}
</div>
);
implementForm.js
<Field
name="sex"
component={renderSelect}
options={Status}
isClearable={true}
validate={required}
/>
requiredFileMessage.js
const required = value => value ? undefined : 'Required'
The best way I found is to create a transparent input field that will be queried via javascript standard checkValidity. This should have absolute positioning and 100% width & height. You can then bind a listener to the input field for the invalid event created by checkValidity
Here is the code I use. There are alterations for use of value field as well as some styling (for MDB inputs) , but you can just change the classes of input to match your own styles libraries. In this manner your validation styling will be the same as your existing form inputs.
Hopes this helps someone.
/**************************************************************************************
*** Select - New Select Control Using react-select (to stop stupid probs with MDB) ***
**************************************************************************************/
// Use This progressively instead of InputSelect
/* Differences from native ReactSelect
Validation - Applies transparent input overlay that works with form checkValidity()
value: This is the record.field value not an object {label: x, value: y} as for react-select
grouped: Explicitly set grouped options when set true
objValue: Works the same as react-select value object
*/
// Note: value property works differently do react-select , use ObjValue to work same as react-select
export const Select = (props) => {
let { id, options, cols, value, objValue, label, grouped, ...o } = props
id = id ? id : 'react-select'
const [selected, setSelected] = useState(value)
const [invalid, setInvalid] = useState(false)
//DEFAULTS
if (!grouped) grouped = false
//--------------------------
// VALIDATION EVENT HANDLERS
//--------------------------
useEffect(() => {
//ADD EVENT HANDLER FOR FOR VALIDATION
let ele = document.getElementById(`${id}_invalid}`)
ele.addEventListener('invalid', (e) => {
console.log('e is ', selected, e)
if (typeof selected === 'undefined' || selected !== null) setInvalid(true)
})
//ON COMPONENT EXIT - REMOVE EVENT HANDLER
return () => {
ele.removeEventListener('invalid', () => {
setInvalid(false)
})
}
// eslint-disable-next-line
}, [])
//Value property (Allows Single field assignent) - translates to object in for {label:x, value:y}
useEffect(() => {
let val
if (grouped) {
val = _.findInGroup(options, 'options', (rec) => rec.value === value)
} else {
val = options.find((rec) => rec.value === value)
}
//console.log('Selected==>', val)
setSelected(val)
// eslint-disable-next-line
}, [value, options])
//objValue Property (Emulate standard react-select value object)
useEffect(() => {
if (objValue) {
setSelected(objValue)
}
// eslint-disable-next-line
}, [objValue])
//STYLING SAME AS MDB INPUT COMPONENTS
const customStyles = {
valueContainer: (provided, state) => ({
...provided,
backgroundColor: 'aliceblue',
}),
dropdownIndicator: (provided, state) => ({
...provided,
backgroundColor: 'aliceblue',
}),
}
const handleChange = (opt, i) => {
setSelected(opt)
//Callback function (i is used for nested data in record)
if (props && props.onChange) props.onChange(opt, i)
}
return (
<Col cols={cols}>
{label && <label className='tp-label text-uppercase'>{label}</label>}
<div className='select-wrapper'>
<ReactSelect
styles={customStyles}
value={selected ? selected : ''}
options={options}
onChange={(val, i) => handleChange(val, i)}
isSearchable={true}
{...o}
/>
<input
id={`${id}_invalid}`}
name={`${id}_invalid}`}
value={selected ? selected : ''}
onChange={() => {}}
tabIndex={-1}
className={`form-control tp-input w-100 ${invalid ? '' : 'd-none'}`}
autoComplete='off'
//value={selected}
onFocus={() => {
setInvalid(false)
}}
style={{
position: 'absolute',
color: 'transparent',
backgroundColor: 'transparent',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 0,
}}
required={true}
/>
</div>
</Col>
)
}
look at this link:https://codesandbox.io/s/react-hook-form-controller-079xx?file=/src/index.js
you have to use Controller
<Controller
control={control}
rules={{ required: true }}
name="citySelect"
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
options={citiesOption}
value={city}
onChange={handleCity}
className="basic-single"
classNamePrefix="select"
isRtl
placeholder="استان مورد نظر"
as={Select}
/>
{errors.citySelect && <p>choose a city</p>}
React-Select style for Bootstrap 5 validation
class CustomSelect extends React.Component {
render() {
const { valid, invalid } = this.props;
let borderColor = '#ced4da';
let focusBorderColor = '#66afe9';
let focusBoxShadow = '0 0 0 .2rem rgba(0, 123, 255, 0.25)';
if (valid) {
borderColor = '#198754';
focusBorderColor = '#198754';
focusBoxShadow = '0 0 0 .2rem rgba(25, 135, 84, .25)';
} else if (invalid) {
borderColor = '#dc3545';
focusBorderColor = '#dc3545';
focusBoxShadow = '0 0 0 .2rem rgba(220, 53, 69, .25)';
}
const customStyles = {
valueContainer: (provided, state) => ({
...provided,
borderColor: state.selectProps.menuIsOpen ? focusBorderColor : borderColor,
boxShadow: state.selectProps.menuIsOpen ? focusBoxShadow : 'none',
}),
};
return (
<Select styles={customStyles} {...this.props} />
);
}
}
Related
what i am creating here is a sorter with 3 inputs like shown here . This sorter will get some data from a table. Now i'm setting state with initSelect and i'm passing it the fields array but when i console.log(select) it gives me the object shown in the image which is incorrect from the behaviours i want {sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
The first input has to have a default value of 'Date' always , but it can change to other values in the dropdown list like name , username ect . When i console log the select state it is messed up as it's always selecting the last one on the fields array , how can i change the initSelects function to correctly build the object i want.
Also the tricky thing which i can't seem to do is , if this Date value is selcted , in the second input, the date value should not be there. And if in the second input we select another value like Earth , Earth and Date should not be in the 3rd input and so on. So basically it means filtering out values . I need serious help as this is for the company i work on
Excepted Behaviour: Dynamically update value every time i select one input element like
{sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
When selectin 'Date' for example , it shouldn't not be included in the dropdownlist on sorterParam2, sorterParam3.
/*eslint-disable*/
import React, { useState, useMemo } from 'react';
import TextField from '#mui/material/TextField';
import Button from '#mui/material/Button';
import { GridSortModel } from '#mui/x-data-grid';
import SorterField from './SorterField';
const initSelects = (fields) => {
let object = {};
fields.map((item) => {
console.log(item, 'item');
object = {
...item,
[item.name]: item.defaultValue ? item.defaultValue : '',
};
});
return object;
};
const Sorter = ({ menuItemsValue, setSortData }: SortProps) => {
const fields: SorterProps[] = [
{
name: 'sorterParam1',
title: 'Sort by',
optional: false,
defaultValue: 'Date',
},
{
name: 'sorterParam2',
title: 'Then by',
optional: true,
},
{
name: 'sorterParam3',
title: 'Then by',
optional: true,
},
];
const [select, setSelect] = useState<any>(() => initSelects(fields));
const getMenuItems = useMemo(() => {
return menuItemsValue.filter((item) => select.sorterParam1 !== item);
}, [menuItemsValue, select]);
const handleSelectChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
console.log(111, name, value);
setSelect({ ...select, [name]: value });
setSortData(sortOptions);
};
const handleClearAllInputs = () => {
setSelect({
sorterParam1: '',
sorterParam2: '',
sorterParam3: '',
});
};
const handleConfirm = () => {};
return (
<TextField
label="Advanced Sorting"
className={styles.sorter__inputs}
id="sorter-parameter-1"
variant="standard"
InputProps={{
disableUnderline: true,
}}
select
SelectProps={{
IconComponent: (props) => <NewIcon {...props} />,
}}
sx={{
fontSize: '12px',
width: '100%',
'& .MuiInputBase-input:focus': {
backgroundColor: 'transparent !important',
},
'& .MuiInputLabel-root': {
color: '#9E9E9E',
},
'& .MuiTextField-root': {
fontSize: '13px',
},
'& .MuiOutlinedInput-root': {
backgroundColor: '#fff',
},
}}
>
{fields.map((option, index) => (
<SorterField
key={option.name}
menuItemsValue={getMenuItems}
name={option.name}
option={option}
count={fields.length}
handleChange={handleSelectChange}
index={index + 1} // setData={setData}
/>
))}
<div className={styles.sorter__inputControllers}>
<Button
className={styles.sorter__clearAllInput}
onClick={() => handleClearAllInputs()}
>
Clear All
</Button>
<Button
onClick={() => handleConfirm()}
className={styles.sorter__confirmInput}
>
Confirm
</Button>
</div>
</TextField>
);
};
export default Sorter;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is the SorterField Component code if that might be helpful
/*eslint-disable*/
import React, { useState } from 'react';
import TextField from '#mui/material/TextField';
import { MenuItem } from '#mui/material';
import { SorterProps } from '../../types/Sorter';
import { ReactComponent as SorterLine } from '../../assets/img/sortLine.svg';
import styles from '../../assets/components/Sorter/sorter.module.scss';
type SorterFieldProps = {
menuItemsValue: string[];
option: SorterProps;
count: number;
name: string;
handleChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
index: number;
};
function SorterField({
option,
count,
menuItemsValue,
handleChange,
index,
}: SorterFieldProps) {
const handleSorting = () => {};
return (
<div className={styles.sorter__container}>
<div className={styles.sorter__header}>
<p className={styles.sorter__label}>
{option.title}{' '}
{option.optional && (
<sup className={styles.sorter__optional}>*Optional</sup>
)}
</p>
<div className={styles.sorter__numbers__container}>
{Array.from({ length: count }, (_, i) => i + 1).map((number) => (
<>
{number === index ? (
<>
<span className={styles.sorter__number}>{index}</span>
</>
) : (
<>
<span className={styles.sorter__numbers}>{number}</span>
</>
)}
</>
))}
</div>
</div>
<div className={styles.sorter__inputs}>
<TextField
className={[styles.sorter__input, styles.sorter__field__input].join(
' '
)}
variant="outlined"
label="Select"
select
SelectProps={{
IconComponent: () => <NewIcon />,
}}
value={option.defaultValue}
onChange={handleChange}
name={option.title}
size="small"
>
{menuItemsValue.map((title, idx) => (
<MenuItem key={idx} value={title}>
{title}
</MenuItem>
))}
</TextField>
<div onClick={handleSorting} className={styles.sorter__sortOrder}>
<div className={styles.sorter__sortOrder__alphabetical}>
<span>A</span>
<span>Z</span>
</div>
<div className={styles.sorter__sortOrder__sortLine}>
<SorterLine />
</div>
</div>
</div>
</div>
);
}
export default SorterField;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You're re-assigning a value to object in each iteration of fields.map().
If you want to build object as a map of field item name to defaultValue (or blank string), use this instead...
return Object.fromEntries(
fields.map(({ name, defaultValue }) => [name, defaultValue ?? ""])
);
See Object.fromEntries()
Also, move the fields declaration outside your component. It's static content so can be omitted from being declared every render and from any hook dependencies.
You could also use fields.reduce() which is basically the same thing
return fields.reduce(
(obj, { name, defaultValue }) => ({
...obj,
[name]: defaultValue ?? "",
}),
{}
);
As for removing selected options as you iterate through the fields, that's a little trickier.
You could use a memo hook to create an iterable data structure that includes the available options for that particular iteration.
For example
const fieldsWithOptions = useMemo(() => {
const taken = new Set(Object.values(select));
const available = menuItemsValue.filter((item) => !taken.has(item));
return fields.map((field) => ({
...field,
options: select[field.name]
? [select[field.name], ...available] // include the current selection
: available,
}));
}, [menuItemsValue, select]);
Then map over fieldsWithOptions instead of fields and use option.options instead of getMenuItems.
I'm having trouble updating knobs when adding custom onchange
export default {
component: Input,
decorators: [
(Story) => <div style={{ textAlign: 'center', width: '70%' }}><Story /></div>,
],
title: 'components/Input',
} as ComponentMeta<typeof Input>;
const Template: ComponentStory<typeof Input> = (args) => {
const [value, setValue] = React.useState(args.value);
return <Input
{...args}
onChange={e => { setValue(e.target.value); args.onChange(e); }}
onClear={() => { setValue(''); args.onClear(); }}
value={value}
/>;
};
After typing a value in knobs onchange the input value does not update and vice versa after typing something in input the value in knobs does not update
I'm not able to put my fetch data as a defaultValue in my Dropdown (react-select) and Checkboxe? I have the displayed dropdown ("Good",...) but the defaultValue for the dishId:1 is Medium, so I'm supposed to see Medium already selected in my Dropdown, which is not the case (same issue for comment).
export default function MenuItemDisplay() {
...
const [dish, setDish] = useState([])
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes [0])
})
.catch(err => {
console.log(err)
})
}, [dishId]);
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
const COMMENT = [
{ label: "0", value: "0" },
...
];
function Checkbox({ value }) {
const [checked, setChecked] = React.useState(true);
return (
<label>
<input
type="checkbox"
defaultChecked={checked}
onChange={() => setChecked(!checked)}
/>
{value}
</label>
);
}
return (
<>
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti={true}
/>
<Dropdown
style={styles.select}
options={COMMENT}
defaultValue={TASTE.find((t) => t.label === dish.comment)}
isMulti={true}
/>
<Checkbox value={!!dish.trust} />
{(dish.menu) !== "") ?
<div>
Menu
<div >
{dish.menu}
</div>
</div>
: ""
}
</>
);
}
export default function CustomDropdown({
className,
style,
options,
styleSelect,
value,
setValue,
isMulti = false
}) {
const styles = {
select: {
width: "100%",
maxWidth: 200
}
};
function changeSelect(option) {
setValue(option.value);
}
return (
<div style={style} onClick={(e) => e.preventDefault()}>
{value && isMulti === false ? (
<Tag
selected={value}
setSelected={setValue}
styleSelect={styleSelect}
/>
) : (
<Select
className={className}
style={styles.select}
value={value}
onChange={changeSelect}
options={options}
isMulti={isMulti}
/>
)}
</div>
);
}
export default function Tag({ selected, setSelected, styleSelect }) {
const backgroundColor = styleSelect?.(selected?.label) ?? "grey";
return (
<div style={{
display: "flex",
justifyContent: "space-around",
padding: "0.1em",
backgroundColor: backgroundColor,
borderRadius: "4px",
color: "white",
marginRight: "20px",
marginLeft: "4px",
marginBottom: "8px"
}}>
{selected}
<button
style={{...}}
onClick={() => setSelected(null)}
>x</button>
</div>
)
}
I really don't understand why is it not working since dish.taste returns me "Medium", dish.comment returns me 5, dish.trust give me 1.
What's wrong with my Dropdown and/or Tag component?
Here there should be the main problem:
<Dropdown
defaultValue={TASTE.find((t) => t.label === dish.taste)}
/>
With this defaultValue prop you are only setting the value once, when the dropdown renders. The dish.taste can not be defined at that time if you retrieve its value through an async request. What you should do instead is adding a useEffect that sets the right value of the dropdown only after it has defined the dish.taste value. So:
const [valueDropdown, setValueDropdown] = React.useState(null);
useEffect(() => {
// here you are running this useEffect each time the dish state changes
if(dish?.taste){ //checks if taste exists
setValueDropdown(TASTE.find((t) => t.label === dish.taste));
}
}, [dish]);
Now, instead of passing to the dropdown the defaultValue you should pass two new props: the value and the setValue:
<Dropdown
value={valueDropdown}
setValue={setValueDropdown}
/>
Lastly, with the change of the props you should also update them inside the Dropdown component and remove the state definition inside that component:
export default function CustomDropdown({
style,
options,
styleSelect,
value : selected,
setValue : setSelected
}) {
// remove the line const [selected, setSelected] = useState(defaultValue);
....other logic
}
A possible fix:
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.find((d) => d.id === dishId))}
isMulti={true}
/>
The other part I could not undestand what you're trying to do, but it is wrong. As Cristiano replyied, you are comparing different types using !==, so it will never be a true condition. Maybe use the same fix as above?
(dish.find((d) => d.id === dishId).menu !== "")
EDIT AND REEDIT
Given more information, this could fix:
export default function MenuItemDisplay() {
...
const [dish, setDish] = useState(null)
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
// make your dish variable not array
setDish(res.data.dishes[0])
})
.catch(err => {
console.log(err)
})
}, [dishId]);
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
if (!dish) {
return (
<div>Loading</div>
);
}
return (
<>
<Dropdown
style={styles.select}
options={TASTE}
// remove map cuz it is not array
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti={true}
/>
{/* remove map cuz it is not array */}
{dish.menu !== "") ?
<div>
Menu
<div >
{question.map((e) => e.menu)}
</div>
</div>
: ""
}
</>
);
}
You are comparing a string (t.label) using === operator with array (result of dish.map) in defaultValue definition.
Try to change it.
Issue
I think the issue is simply that that defaultValue prop of a React component is expected to exist when the component mounts and never change. It's the default value when the component mounts. The code in your example is comparing an element in the TASTE array against the initial dish state, which is an array but accessed as an object. dish.taste of [] is undefined.
Solutions
Uncontrolled Component
If you want the Dropdown component to remain an uncontrolled component then it should be conditionally rendered only when the dish state has been initialized by the useEffect hook and the POST request.
const [dish, setDish] = useState(); // initially undefined
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes[0]);
})
.catch(err => {
console.log(err);
});
}, [dishId]);
...
{dish.taste && ( // conditionally render the dropdown
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti
/>
)}
Controlled Component
If you want the Dropdown component to be controlled, then use the (likely) value prop instead. It's meant to be changed during the life of the component.
const [dish, setDish] = useState(); // initially undefined
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes[0]);
})
.catch(err => {
console.log(err);
});
}, [dishId]);
...
<Dropdown
style={styles.select}
options={TASTE}
value={TASTE.find((t) => t.label === dish.taste)} // use value prop
isMulti
/>
I am trying to use react-select. I have certain condition (boolean), when the param is true, some property/attribute of the react-select would change base on the logic. One if them is menuList. My objective, if the param is true, I want the menuList displayed and searchable, but when false, I want the menuList hidden but still available (not disabled, thats why I use onChange and onInputChange prop). Here is what I've set so far:
const isExist = true;
return (
<div style={{ width: '50%', margin: 20 }}>
<Select
id="asd"
value={selectedOption}
onChange={isExist ? this.handleChange : null}
onInputChange={isExist ? null : e => this.tests(e) }
options={options}
isClearable={true}
styles={style}
placeholder="Please type"
noOptionsMessage={() => isExist ? 'Zero Result' : null}
components={{ MenuList: () => isExist ? 'display the menu but how?' : null }}
/>
</div>
);
any help would be helpful. Thank you!
Based on the behaviour you describe you could use a controlled menuIsOpen props like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
isExist: false,
menuIsOpen: false,
selectedOption: null
};
}
onChange = e => {
this.setState({
selectedOption: e
});
};
onInputChange = (options, { action }) => {
if (this.state.isExist) {
if (action === "menu-close") {
this.setState({ menuIsOpen: false });
}
} else {
if (action === "input-change") {
// do whatever you want with the entered value
}
}
};
onFocus = e => {
if (this.state.isExist) this.setState({ menuIsOpen: true });
};
render() {
return (
<div style={{ width: "50%", margin: 20 }}>
<Select
id="asd"
value={this.state.selectedOption}
onFocus={this.onFocus}
onChange={this.onChange}
onInputChange={this.onInputChange}
options={options}
isClearable={true}
placeholder="Please type"
noOptionsMessage={() => (this.state.isExist ? "Zero Result" : null)}
menuIsOpen={this.state.menuIsOpen}
/>
</div>
);
}
}
Here a live example.
I have a material-ui select box that is populated with a state variable. No matter what I have tried, I cannot get the value to actually show when I select an option. Can anyone tell me why? It keeps just giving me a blank bar. I even took an example from another code sandbox and copied it almost exactly. One thing I did notice is that my event.target.value is always undefined, and I am not sure why. So I just use value, instead, in my handleChange function. Any help is greatly appreciated! This has been driving me crazy.
Code Sandbox: https://codesandbox.io/s/jnyq16279v
Code:
import React from 'react';
import MenuItem from 'material-ui/MenuItem';
import Select from 'material-ui/SelectField';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
export default class KKSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
selectOptions: [
{
value: "Image",
id: "1"
},
{
value: "Integer",
id: "2"
},
{
value: "Decimal",
id: "3"
},
{
value: "Boolean",
id: "4"
},
{
value: "Text",
id: "5"
}
],
selectedValue: ""
};
}
renderSelectOptions = () => {
return this.state.selectOptions.map((dt, i) => {
return (
<MenuItem key={i} value={dt.id}>
{dt.value}
</MenuItem>
);
});
}
handleChange = (event, value) => {
this.setState({ selectedValue: value });
};
render() {
return (
<MuiThemeProvider>
<Select
value={this.state.selectedValue}
onChange={this.handleChange}
>
{this.renderSelectOptions()}
</Select>
</MuiThemeProvider>
);
}
}
First of all, you are using material-ui version 0.20.1 - docs for that version is here: https://v0.material-ui.com/#/components/select-field, but it's recommended to use v1 (https://material-ui.com/getting-started/installation/).
For version 0.20.1, there are few problems with your code:
First: renderSelectOptions: {dt.value} should be assigned to MenuItem primaryText
renderSelectOptions = () => {
return this.state.selectOptions.map((dt, i) => {
return (
<MenuItem key={i} value={dt.id}>
{dt.value}
</MenuItem>
);
});
}
like this:
renderSelectOptions = () => {
return this.state.selectOptions.map((dt, i) => (
<MenuItem key={dt.id} value={dt.id} primaryText={dt.value} />
));
};
And second - handle change has event, index and value arguments, so your value is acctually index - not value.
handleChange = (event, value) => {
this.setState({ selectedValue: value });
};
Should be:
handleChange = (event, index, value) => {
this.setState({ selectedValue: value });
};
Check out working version for material-ui version 0.20.1 here: https://codesandbox.io/s/9q3v1746jy
P.S. If you are using material-ui version 1.2.1, I made working example for that version too, you can check it out here: https://codesandbox.io/s/jjvrnokkv3
it could be solve by using custom styling in listbox props . plz refer my code .
<Autocomplete
key={`${filterKey}-township`}
onChange={onAutocompleteChange}
disableClearable
multiple
fullWidth
limitTags={1}
value={value}
disableCloseOnSelect={true}
size="small"
id="township"
options={townships}
getOptionLabel={(option) => option.Township}
ListboxProps={{
style: {
position: "absolute",
top: 10,
bottom: 0,
right: 0,
left: 0,
height:300,
width: "100%",
overflowY: "auto",
backgroundColor: "white",
},
}}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
label="Township"
onKeyDown={handleKeyDown}
/>
)}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox size="small" icon={icon} checkedIcon={checkedIcon} checked={selected} />
<Typography variant="body2">{option.Township}</Typography>
</React.Fragment>
)}
/>