ReactJs: Cannot select item from MenuItem - javascript

I have a case where the I have 3 items, and at in the case where is the first item, it should be displaying only the first item, and not allow the user to select 2nd and 3rd, but in case wher isItFirt = false then the user should be able to choose from the list. I wrote the minimal reproducible example as shown below:
import * as React from "react";
import {
Typography,
Button,
Dialog,
Box,
Select,
InputLabel,
FormControl,
MenuItem,
SelectChangeEvent
} from "#mui/material";
enum MyOptions {
FIRST = 1,
SECOND = 2,
THIRD = 3
}
export default function App() {
const [open, setOpen] = React.useState(true);
const [myOptions, setMyOptions] = React.useState(MyOptions.SECOND as number);
const handleChange = (event: SelectChangeEvent) => {
let nr = parseInt(event.target.value, 10);
setMyOptions(nr);
};
const isItFirst: boolean = false;
const handleClose = () => {
setOpen(false);
};
const somethingHappens = () => {
console.log("clicked: ", myOptions);
setOpen(false);
};
React.useEffect(() => {
if (isItFirst) {
setMyOptions(MyOptions.FIRST as number);
}
}, [isItFirst]);
return (
<div>
<Button
variant="contained"
size="small"
onClick={() => {
setOpen(true);
}}
>
Display dialog
</Button>
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box>
<Typography id="modal-modal-title" variant="h6" component="h4">
Select one of the options
</Typography>
<FormControl>
<InputLabel id="1">Options</InputLabel>
<Select
labelId=""
id=""
value={myOptions}
label="Options"
onChange={(e: any) => handleChange(e)}
>
{isItFirst ? (
<MenuItem value={MyOptions.FIRST}>This is first</MenuItem>
) : (
<div>
<MenuItem value={MyOptions.SECOND} key={MyOptions.SECOND}>
This is second
</MenuItem>
<MenuItem value={MyOptions.THIRD} key={MyOptions.THIRD}>
This is third
</MenuItem>
</div>
)}
</Select>
</FormControl>
</Box>
<Button
variant="contained"
size="small"
onClick={() => {
somethingHappens();
}}
>
Select
</Button>
</Dialog>
</div>
);
}
This is the error output:
MUI: You have provided an out-of-range value `1` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are "".
And this is the dialog box that is shown in the case when isItFirst === false, I do not understand why it is shown as blank when I set the state of myOptions with the help of useEffect.

According to this document for children prop of Select
The option elements to populate the select with. Can be some MenuItem when native is false and option when native is true.
⚠️The MenuItem elements must be direct descendants when native is false.
So technically, we cannot pass div or any other elements to wrap MenuItem.
For the fix, you can consider to use filter and map with a pre-defined option like below
const options: {value: MyOptions, label: string}[] = [
{value: MyOptions.FIRST, label: "This is first"},
{value: MyOptions.SECOND, label: "This is second"},
{value: MyOptions.THIRD, label: "This is thrid"}
]
Here is how we apply options to Select
<Select
labelId=""
id=""
value={myOptions}
label="Options"
onChange={handleChange}
key="first-select"
>
{options
.filter((option) =>
isItFirst
? option.value === MyOptions.FIRST
: option.value !== MyOptions.FIRST
)
.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</Select>

Related

How to make multiple radio groups work in React.js

I'm using React with Chakra UI for this.
I'm having an issue with my radio buttons. I have two groups of radio buttons, second group is showed dynamically based on the first group selection. However, first group works fine, but when selecting an option from the second group it doesn't get marked, but looking at the state of the component it does pass the value.
Below is the code of my component.
const SuperAttack = ({ handleChange }: Props) => {
const [selectedMutiplier, setSelectedMultiplier] = useState<SAMultiplier>();
const handleMultiplierSelect = (name: string) => {
const selected = SAMultipliers.filter((multiplier) => {
return multiplier.name === name;
});
setSelectedMultiplier(selected[0]);
};
return (
<Box
border="1px"
borderColor="gray.200"
borderRadius={10}
p="20px"
mb="10px"
>
<FormControl>
<FormLabel>Super attack multiplier</FormLabel>
<RadioGroup name="attack" onChange={handleMultiplierSelect}>
<Stack direction="row">
{SAMultipliers.map((multiplier) => {
return <Radio value={multiplier.name}>{multiplier.name}</Radio>;
})}
</Stack>
</RadioGroup>
</FormControl>
{selectedMutiplier ? (
<FormControl>
<FormLabel>Select SA level</FormLabel>
<RadioGroup name="multiplier" onChange={handleChange}>
<Stack direction="row">
{selectedMutiplier.SA.map((multiplier) => {
return (
<Radio value={multiplier.value}>{multiplier.name}</Radio>
);
})}
</Stack>
</RadioGroup>
</FormControl>
) : null}
</Box>
);
};
export default SuperAttack;
Thanks in advance

Dynamically adding more `select` and it does not show the value anymore

I was trying to duplicate the select field. However, it does not show any value anymore.
If I'll choose small for the size, this is what it shows in the console.
This is the codesandbox link: https://codesandbox.io/s/basicselect-material-demo-forked-4g34r?file=/demo.js
The codes:
import React, { useState, useEffect } from "react";
import Box from "#mui/material/Box";
import InputLabel from "#mui/material/InputLabel";
import MenuItem from "#mui/material/MenuItem";
import FormControl from "#mui/material/FormControl";
import Select from "#mui/material/Select";
import { TextField, Button } from "#mui/material";
export default function BasicSelect() {
const [prod, setProd] = useState("");
const [qty, setQty] = useState(0);
const [design, setDesign] = useState("");
const [size, setSize] = useState("");
const handleChange = (event) => {
setProd(event.target.value);
};
const handleChangeSize = (event) => {
setSize(event.target.value);
};
const handleChangeDesign = (event) => {
setDesign(event.target.value);
};
const handleSubmit = async (e) => {
e.preventDefault();
console.log(prod, qty, size, design);
};
const [sizeList, setSizeList] = useState([{ size: "" }]);
console.log(sizeList);
//helper method
const handleAdd = () => {
setSizeList([...sizeList, { size: "" }]);
};
const handleRemove = (index) => {
const list = [...sizeList];
list.splice(index, 1);
setSizeList(list);
};
const handleSizeChange = (e, index) => {
const { name, value } = e.target.value;
setSize(e.target.value);
const list = [...sizeList];
list[index][name] = value;
setSizeList(list);
};
return (
<Box sx={{ minWidth: 120 }}>
<form onSubmit={handleSubmit}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Product</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={prod}
label="Product"
onChange={handleChange}
>
<MenuItem value="Item1">Item1</MenuItem>
<MenuItem value="Item2">Item2</MenuItem>
<MenuItem value="Item3">Item3</MenuItem>
</Select>
</FormControl>
<br />
<br />
{/* <TextField
type="number"
label="Quantity"
variant="outlined"
value={qty}
onChange={(e) => setQty(e.target.value)}
fullWidth
/> */}
<br />
<br />
{sizeList.map((singleSize, index) => (
<div key={index}>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Size</InputLabel>
<Select
labelId="demo-simple-select-label"
id="size"
value={singleSize.size}
label="Product"
// onChange={handleChangeSize}
onChange={(e) => handleSizeChange(e, index)}
>
<MenuItem value="S">Small</MenuItem>
<MenuItem value="M">Medium</MenuItem>
<MenuItem value="L">Large</MenuItem>
</Select>
</FormControl>
<br />
<br />
{sizeList.length > 1 && (
<Button
onClick={() => handleRemove(index)}
variant="contained"
color="secondary"
>
Remove{" "}
</Button>
)}
<br />
<br />
{sizeList.length - 1 === index && (
<Button variant="contained" onClick={handleAdd}>
{" "}
Add Quantity
</Button>
)}
</div>
))}
<br />
<br />
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Choose Design</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={design}
label="Product"
onChange={handleChangeDesign}
>
<MenuItem value="Design1">Design1</MenuItem>
<MenuItem value="Design2">Design2</MenuItem>
<MenuItem value="Design3">Design3</MenuItem>
</Select>
</FormControl>
<br />
<br />
<Button type="submit">Submit </Button>
</form>
<Button>Add more Product </Button>
</Box>
);
}
Any help would be appreciated. Thank you
Update:
Link: https://codesandbox.io/s/form-1-ls6rx?file=/demo.js
The value of the select now shows. But it does not update correctly in the console.
If I'll select M in the first field. The console shows {size: " "}. Adding another quantity and selecting a size. This is what console shows {size: 'M'} {size: ''}
console:
What could I use to update it according to what was selected?
The size selects don't have name props, and as the handler is specific to these selects, you can update the value of the key size. You can also use a functional state update as the new state depends on the previous state.
Updated change handler:
const handleSizeChange = (e, index) => {
const { value } = e.target;
setSizeList((prev) =>
Object.assign([...prev], { [index]: { size: value } })
);
};
Update
You can use useEffect if you want to check the sizeList after it updates:
useEffect(() => {console.log(sizeList)}, [sizeList])

Multiple Select with options as an object array

I have the following component for selecting roles:
export const MultipleSelectChip = ({
options,
label,
error,
onRolesUpdate,
}: Props) => {
const theme = useTheme();
const [selectedOptions, setSelectedOptions] = React.useState<string[]>([]);
const handleChipChange = (
event: SelectChangeEvent<typeof selectedOptions>,
) => {
const {
target: { value },
} = event;
setSelectedOptions(
// On autofill we get a the stringified value.
typeof value === 'string' ? value.split(',') : value,
);
};
return (
<div>
<FormControl sx={{ m: 1, width: 300 }}>
<InputLabel id="multiple-chip-label">{label}</InputLabel>
<Select
required
labelId="multiple-chip-label"
error={error}
id="demo-multiple-chip"
multiple
value={selectedOptions}
onChange={handleChipChange}
input={<OutlinedInput id="select-multiple-chip" label={label} />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value} label={value} />
))}
</Box>
)}
MenuProps={MenuProps}
>
{options.map((propOption) => (
<MenuItem
key={propOption.id}
value={propOption.name}
style={getStyles(propOption, selectedOptions, theme)}
>
{propOption.name}
</MenuItem>
))}
</Select>
<FormHelperText>Here's my helper text</FormHelperText>
</FormControl>
</div>
);
};
For options I have an array of objects with id and name, the thing is that I want to use the names for displaying the chips and the ids to pass them to the parent component for the add request. I don't know how to get de ids, too.
This is the example: https://codesandbox.io/s/6ry5y?file=/demo.tsx but is using an array of strings instead of an array of objects.
This is how 'options' looks like:
const rolesDummy: Role[] = [
{ id: '61fb0f25-34aa-46c6-8683-093254223dcd', name: 'HR' },
{ id: '949b9b1e-d3f8-45cb-a061-08da483bd486', name: 'Interviewer' },
{ id: 'c09ae2d4-1335-4ef0-8d4b-ee9529796b52', name: 'Hiring Manager' },
];
And I need to get back only the selected ids
Thank you!
If you pass the option as an object, you can render each MenuItem with the option.id as a key and the option.name as the label. The MenuItem is identified by an id:
<Select {...}>
{options.map((option) => (
<MenuItem key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
To display the name in the selected Chip. Use renderValue, but it only provides you the selected values (array of option.id), so you need to find the option to get the name:
renderValue={(selected) => {
return (
<Box>
{selected.map((value) => {
const option = options.find((o) => o.id === value);
return <Chip key={value} label={option.name} />;
})}
</Box>
);
}}
Now you can get an array of selected ids by adding a change handler:
onChange={e => console.log(e.target.value)}

React - You have provided an out-of-range value `undefined` for the select component

I'm creating a material-UI dialogue form that posts data to my API. One of the fields in my backend database is binary and takes in only two possible options. How can I reflect that in my dialogue code below?
Here is my Fulltrace Back error:
Material-UI: You have provided an out-of-range value undefined for
the select (name="category") component. Consider providing a value
that matches one of the available options or ''. The available values
are personal, social.
The possible options for this specific field are either personal or social.
I tried doing this, letting my dialogue push the correct responses:
<MenuItem value={'personal'}> personal </MenuItem>
<MenuItem value={'social'}> social </MenuItem>
But that does not work and I understand why. Just not sure how to solve the error now as I'm not too savvy with React/JS in general.
export default function CreateBucket() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const history = useHistory();
const initialFormData = Object.freeze({
name: '',
category: '',
about: '',
});
const [formData, updateFormData] = useState(initialFormData);
const handleChange = (e) => {
updateFormData({
...formData,
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
axiosInstance
.post(`create/bucket`, {
name: formData.name,
category: formData.category,
about: formData.about,
})
.then((res) => {
history.push('/');
console.log(res);
console.log(res.data);
});
};
const classes = useStyles();
return(
<Fragment>
<Fab color="primary" aria-label="add" onClick={handleClickOpen} variant="extended">
<AddIcon className={classes.extendedIcon}/>
Create Bucket
</Fab>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Create your Bucket</DialogTitle>
<DialogContent>
<DialogContentText>
Get started! Make your bucket by telling us a little bit more.
</DialogContentText>
<form>
<FormControl>
<InputLabel> What type of Bucket </InputLabel>
<Select
id="category"
label="Bucket Type"
name="category"
fullWidth
required
variant="filled"
onChange={handleChange}
margin="normal"
className={classes.formControl}
>
<MenuItem value={'personal'}> personal </MenuItem>
<MenuItem value={'social'}> social </MenuItem>
</Select>
</FormControl>
</form>
</DialogContent>
<DialogActions>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={handleSubmit}
>
Create Bucket
</Button>
</DialogActions>
</Dialog>
</Fragment>
);
}
How can I implement changes to solve my traceback error?
Make sure you provide value prop to <Select> component, put value="" in case you don't want any of the options to be selected by default or value="personal" in case you want personal to be selected by default.
Her is mine with value={formData.category} it takes the value selected from state.
export default function CreateBucket() {
const [open, setOpen] = React.useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const history = useHistory();
const initialFormData = Object.freeze({
name: '',
category: '',
about: '',
});
const [formData, updateFormData] = useState(initialFormData);
const handleChange = (e) => {
updateFormData({
...formData,
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
axiosInstance
.post(`create/bucket`, {
name: formData.name,
category: formData.category,
about: formData.about,
})
.then((res) => {
history.push('/');
console.log(res);
console.log(res.data);
});
};
const classes = useStyles();
return(
<Fragment>
<Fab color="primary" aria-label="add" onClick={handleClickOpen} variant="extended">
<AddIcon className={classes.extendedIcon}/>
Create Bucket
</Fab>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Create your Bucket</DialogTitle>
<DialogContent>
<DialogContentText>
Get started! Make your bucket by telling us a little bit more.
</DialogContentText>
<form>
<FormControl>
<InputLabel> What type of Bucket </InputLabel>
<Select
id="category"
label="Bucket Type"
name="category"
fullWidth
required
variant="filled"
onChange={handleChange}
margin="normal"
value={formData.category}
className={classes.formControl}
>
<MenuItem value={'personal'}> personal </MenuItem>
<MenuItem value={'social'}> social </MenuItem>
</Select>
</FormControl>
</form>
</DialogContent>
<DialogActions>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
className={classes.submit}
onClick={handleSubmit}
>
Create Bucket
</Button>
</DialogActions>
</Dialog>
</Fragment>
);
}
It is exactly what it says on the tin:
Material-UI: You have provided an out-of-range value undefined for the select (name="category") component. Consider providing a value that matches one of the available options or ''. The available values are personal, social.
When you create a Select it wants you to provide initial value, which can be personal, social or an empty string. You don't provide any value, so it get confused
<Select
id="category"
value={formData.category}
label="Bucket Type"
will make it stop complaining.
None of these solved my issue where I wanted to have a placeholder but I have a state value and an onChange event inside my textfield. Once you set the initial state you cannot have a placeholder.
My fix below is to have the value of 'place holder text' in initial state and inside the list of options. I disabled and grayed out the value once initially loaded so it looks like a placeholder.
<TextField
margin="dense"
select
fullWidth
placeholder='Fuel Type'
value={fuelType}
onChange={handleFuelChange}
// defaultValue="Fuel Type"
type="text"
name="fuelType"
>
{options3.map((option) => {
return (option.label === "Fuel Type" ? <MenuItem key={option.label} disabled value={option.label + "," + option.carbonFactor}>
<span style={{ color: "rgb(156, 156, 156)" }}>{option.label}</span>
</MenuItem> : <MenuItem key={option.label} value={option.label + "," + option.carbonFactor}>
{option.label}
</MenuItem>)
})}
</TextField>

Select all and Select None buttons in Autocomplete Material UI React

I want to implement two buttons Select All and Select None inside Autocomplete React Material UI along with checkbox for each option.When Select All button is clicked all the options must be checked and when I click Select None all the options must be unchecked.
How do I implement that ?
<Autocomplete
id={id }
size={size}
multiple={multiple}
value={value}
disabled={disabled}
options={items}
onChange={handleChange}
getOptionLabel={option => option.label}
renderOption={(option, { selected }) => (
<React.Fragment >
{isCheckBox(check, selected)}
{option.label}
</React.Fragment>
)}
renderInput={params => (
<TextField id="dropdown_input"
{...params} label="controlled" variant={variant} label={label} placeholder={placeholder} />
)}
/>
export function isCheckBox(check, selected) {
if (check) {
const CheckBox = <Checkbox
id="dropdown_check"
icon={icon}
checkedIcon={checkedIcon}
checked={selected}
/>
return CheckBox;
}
return null;
}
I stumbled into the same issue earlier today.
The trick is to use local state to manage what has been selected, and change the renderOption to select * checkboxes if the local state has the 'all' key in it.
NB: At the time of writing React 16 is what I'm working with
I'm on a deadline, so I'll leave a codesandbox solution for you instead of a rushed explanation. Hope it helps :
Select All AutoComplete Sandbox
Updated
for React version 16.13.1 and later. codesandbox
const [open, setOpen] = useState(false);
const timer = useRef(-1);
const setOpenByTimer = (isOpen) => {
clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
setOpen(isOpen);
}, 200);
}
const MyPopper = function (props) {
const addAllClick = (e) => {
clearTimeout(timer.current);
console.log('Add All');
}
const clearClick = (e) => {
clearTimeout(timer.current);
console.log('Clear');
}
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
return (
<Autocomplete
PopperComponent={MyPopper}
onOpen={(e) => {
console.log('onOpen');
setOpenByTimer(true);
}}
onClose={(obj,reason) => {
console.log('onClose', reason);
setOpenByTimer(false);
}}
open={open}
.....
....
/>
);
Old Answer
Just customise PopperComponent and do whatever you want.
Autocomplete API
const addAllClick = (e: any) => {
setValue(items);
};
const clearClick = (e: any) => {
setValue([]);
};
const MyPopper = function (props: any) {
return (
<Popper {...props}>
<ButtonGroup color="primary" aria-label="outlined primary button group">
<Button color="primary" onClick={addAllClick}>
Add All
</Button>
<Button color="primary" onClick={clearClick}>
Clear
</Button>
</ButtonGroup>
{props.children}
</Popper>
);
};
<Autocomplete
PopperComponent={MyPopper}
...
/>
If you want to make an autocomplete with select all option using react material ui and react hook form, you can implement to Autocomplete like so
multiple: To allow multiple selection
disableCloseOnSelect: To disable the close of the box after each selection
options: Array of items of selection
value: Selected options.
getOptionLabel: The string value of the option in our case is name
filterOptions: A function that determines the filtered options to be rendered on search, in our case we used it to add selectAll checkbox.
renderOption: Render the option, use getOptionLabel by default.
renderInput: To render the input,
onChange: Callback fired when the value changes
Now you can play with selected values using handleChange so once the select is fired check if the selected option is select all if yes then set the newest selectedOptions
<Autocomplete
multiple
disableCloseOnSelect
options={items}
value={selectedOptions}
getOptionLabel={(option) => option.name}
filterOptions={(options, params) => {
const filtered = filter(options, params)
return [{ id: 0, name: selectAllLabel }, ...filtered]
}}
renderOption={(props, option, { selected }) => {
// To control the state of 'select-all' checkbox
const selectAllProps =
option.name === 'Sélectionner Tous' ? { checked: allSelected } : {}
return (
<li {...props}>
<Checkbox checked={selected} {...selectAllProps} />
{option.name}
</li>
)
}}
renderInput={(params) => (
<TextField {...params} label={label} placeholder={label} />
)}
onChange={handleChange}
/>
you can refer to the Autocomplete API to get detailed definition of each item
You can refer to this codeSendBox to check a demo of react material Autocomplete with select all using react material ui version 5 and react hook form verion 7

Categories