The Material UI documentation includes an example of a multiple select where the selected options are rendered with the Chip component by using the renderValue prop on the Select. The standard behavior for the Select component is that clicking on the current value opens the list of options.
I am trying to tweak this so that the Chips show the X button, and clicking on the X should instantly remove that item from the selections rather than opening the options list.
That seems easy, but I can't get the onDelete event of the Chip to fire. Clicking the X still just opens the Select.
How can I get the onDelete event to take priority? From what I know about event bubbling, it seems like the Chip should handle the event first.
Code Sandbox Demo
Code:
const MultipleSelectDemo = () => {
const [personName, setPersonName] = React.useState<string[]>(initialSelected);
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
setPersonName(event.target.value as string[]);
};
// this never gets called
const handleDelete = (e: React.MouseEvent, value: string) => {
e.preventDefault();
console.log("clicked delete");
setPersonName((current) => _without(current, value));
};
return (
<div>
<FormControl>
<InputLabel id="demo-mutiple-chip-checkbox-label">
Chip + Check
</InputLabel>
<Select
labelId="demo-mutiple-chip-checkbox-label"
id="demo-mutiple-chip-checkbox"
multiple
value={personName}
onChange={handleChange}
onOpen={() => console.log("select opened")}
IconComponent={KeyboardArrowDownIcon}
renderValue={(selected) => (
<div>
{(selected as string[]).map((value) => (
<Chip
key={value}
label={value}
clickable
className={classes.chip}
onDelete={(e) => handleDelete(e, value)}
onClick={() => console.log("clicked chip")}
/>
))}
</div>
)}
>
{names.map((name) => (
<MenuItem key={name} value={name}>
<Checkbox checked={personName.includes(name)} />
<ListItemText primary={name} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
);
}
The opening of the Select is triggered by the mouse-down event -- not the click event.
You can get your desired behavior by stopping propagation of the mouse-down event when it occurs on the delete icon of the Chip:
<Chip
key={value}
label={value}
clickable
deleteIcon={
<CancelIcon
onMouseDown={(event) => event.stopPropagation()}
/>
}
className={classes.chip}
onDelete={(e) => handleDelete(e, value)}
onClick={() => console.log("clicked chip")}
/>
For people who still find answer for this question, MUI has a component does exactly what you want: Autocomplete, you can find it here: https://mui.com/material-ui/react-autocomplete/
Here is the code example from MUI documents: Code Sandbox Demo
Related
I have a list of users and a Select dropdown from material UI with some values. I am able to console.log the values of the select but how can I know to which user in the List they refer to?
<List>
{userList.map((user:any) => (
<ListItem key={user.key}>
<ListItemAvatar>
<Avatar>
<PersonIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={user.name} />
<Select
value={userValue}
onChange={handleChange}
>
{dropdownvalues.map(
(g: { value: string}) => (
<MenuItem key={g.value} value={g.value}>
{g.value}
</MenuItem>
)
)}
</Select>
</ListItem>
))}
</List>
const handleChange=(e:any,index:any) => {
console.log(e.target.value)//here I am able to console log just the value how can I bind the user too given the fact that this funciton doesnt accept another parameter
}
Just add index as value as it is uniquely identifiable in the following code -
{dropdownvalues.map(
(g: { value: string},index:number) => (
<MenuItem key={g.value} value={index}>
{g.value}
</MenuItem>
)
)}
After that just access your selected user as -
const handleChange = (e:any) => {
const selectedInd = e.target.value;
console.log('index->',selectedInd);
console.log(dropdownvalues[e.target.value]);
}
Simplest way is to extend your handleChange function and call like this:
// ...
onChange={(evt) => handleChange(user)}
// ... And then extend the function:
const handleChange=(user:any) => {
console.log(user)
}
---GOAL---
I am attempting to have a Material UI select with MenuItems that are wrapped in HtmlTooltips, which display hover information about each list choice.
I'm keeping it simple intentionally now as a PoC and will map it dynamically later.
---CURRENT CODE---
I am using the controller/view pattern and have the following material UI Select component in the view:
<FormControl
variant="outlined"
>
<InputLabel id="owner">Owner</InputLabel>
<Select
labelId="owner"
id="owner"
defaultValue="0"
value={props.owner}
onChange={props.handleOwnerChange}
>
<MenuItem id="none" value="none" disabled>
(Select a Value)
</MenuItem>
<MenuItem id="231-abc" value="231-abc" disabled>
Charlie Person
</MenuItem>
<HtmlTooltip
title={
<>
<Typography color="inherit">
Steve Human
</Typography>
<b>{"Next PTO:"}</b>{" "}
<i style={{ color: "red" }}>
{" 12/25/20-01/01/20"}
</i>
</>
}
>
<MenuItem id="123-abc" value="123-abc">
Steve Human
</MenuItem>
</HtmlTooltip>
</Select>
</FormControl>
...in the controller, my state getter/setter...
const [owner, setOwner] = useState("null");
...and also in the controller, a function which the select uses to change the state value when a list item is selected:
const handleOwnerChange = (event) => {
setOwner(event.target.value); //
};
event.target.value is undefined in this case for some reason.
Because it is async when you use setOwner(event.target.value), you need to save the event target value to a variable instead of using it directly in setOwner.
For more details, refer to here.
Event pooling
The SyntheticEvent is pooled. This means that the SyntheticEvent
object will be reused and all properties will be nullified after the
event callback has been invoked. This is for performance reasons. As
such, you cannot access the event in an asynchronous way.
function onClick(event) {
console.log(event); // => nullified object.
console.log(event.type); // => "click"
var eventType = event.type; // => "click"
setTimeout(function() {
console.log(event.type); // => null
console.log(eventType); // => "click"
}, 0);
this.setState({clickEvent: event}); // Won't work. this.state.clickEvent will only contain null values.
this.setState({eventType: event.type}); // You can still export event properties.
}
I figured out what the issue was after working trying Eric's suggestion. Here is what I ended up needing to do:
Basically, I just needed to:
Reverse the order of the component wrapping - Wrap the HtmlTooltip WITH the MenuItem instead of wrapping the MenuItem with the HtmlTooltip.
Enclose the list item text in a div
---WORKING CODE---
I am using the controller/view pattern and have the following material UI Select component in the view:
<FormControl
variant="outlined"
>
<InputLabel id="owner">Owner</InputLabel>
<Select
labelId="owner"
id="owner"
defaultValue="none"
value={props.owner}
onChange={props.handleOwnerChange}
>
<MenuItem id="none" value="none" disabled>
(Select a Value)
</MenuItem>
<MenuItem id="231-abc" value="231-abc" disabled>
Charlie Person
</MenuItem>
<MenuItem id="123-abc" value="123-abc">
<HtmlTooltip
title={
<>
<Typography color="inherit">
Steve Human
</Typography>
<b>{"Next PTO:"}</b>{" "}
<i style={{ color: "red" }}>
{" 12/25/20-01/01/20"}
</i>
</>
}
>
<div>Steve Human</div>
</HtmlTooltip>
</MenuItem>
</Select>
</FormControl>
...in the controller, my state getter/setter...
const [owner, setOwner] = useState("none");
...and also in the controller, a function which the select uses to change the state value when a list item is selected:
const handleOwnerChange = (event) => {
setOwner(event.currentTarget.id);
};
Hello guys I'm using React Material UI select. When I change option I trigger onChange method and add attr on selected value so how can I set fisrt option of items as selected.
value={} **// I want to display first option to selected without trigger on Change method**
onChange={(e) => {
data.forEach(a => {
if (a.Id === e.target.value) {
a.selected = true
} else {
a.selected = false
}
});
}}
>
<MenuItem disabled value=""><em>Please Select</em></MenuItem>
{data.map((item) => {
return (
<MenuItem key={item.Id} value={item.Id} >
{item.Ad}
</MenuItem>
);
})}
</Select>
You need 2 states on your component, data and active. Where data is the items you want to display on the dropdown, and active which indicates what item is currently selected. Commonly, we use useEffect hook to initialize states.
Also, wehave to re-implement your onChange function. I suggest you review the docs on how to update states on functional component https://reactjs.org/docs/hooks-reference.html#usestate.
Anyway, you can change your code to this
export default function App() {
const [data, setData] = React.useState(DATA);
// select the first option by default
const [active, setActive] = React.useState(DATA[0].Id);
function onChange(event) {
setActive(event.target.value);
}
return (
<Select value={active} onChange={onChange} fullWidth>
<MenuItem disabled value="">
<em>Please Select</em>
</MenuItem>
{data.map((item) => (
<MenuItem key={item.Id} value={item.Id}>
{item.Ad}
</MenuItem>
))}
</Select>
);
}
...and I created a codesandbox for you to try it out.
I want to use an Autocomplete (from the Material-Ui library) component from material ui to select multiple options, and those options should not be able to be removed (directly) by the user.
The problem I'm facing is that the user can delete the options from the Autocomplete if they focus the component and press backspace as if they are deleting text.
Code
This is the component I'm using:
<Autocomplete multiple
options={options}
getOptionLabel={option => option.title}
renderInput={params =>
<TextField {...params} label="Autocomplete" variant="outlined" />
}
onChange={this.onAutocompleteChange.bind(this)}
getOptionSelected={(option: Option, value: Option) => option.value === value.value}
filterSelectedOptions={true}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip key={option.value} label={option.title} color="primary" />
))
}
disableClearable={true}
/>
What I tried
Disabling the TextField from the renderInput prop with disable={true} has no effect.
Adding InputProps={{ disabled: true }} or InputProps={{ readOnly: true }} to TextField disables the Autocomplete completely.
Adding ChipProps={{ disabled: true }} to the Autocomplete has no effect.
Thanks for reading!
To control this aspect, you need to use a controlled approach to the Autocomplete's value as demonstrated in this demo.
In the documentation for the onChange prop you will find the following:
onChange Callback fired when the value changes.
Signature:
function(event: object, value: T | T[], reason: string) => void
event: The event source of the callback.
value: The new value of the component.
reason: One of "create-option", "select-option", "remove-option", "blur" or "clear".
The third argument to onChange is the "reason" for the change. In your case, you want to ignore all of the "remove-option" changes:
onChange={(event, newValue, reason) => {
if (reason !== "remove-option") {
setValue(newValue);
}
}}
Here's a full working example:
import React from "react";
import TextField from "#material-ui/core/TextField";
import Autocomplete from "#material-ui/lab/Autocomplete";
import Chip from "#material-ui/core/Chip";
const options = ["Option 1", "Option 2"];
export default function ControllableStates() {
const [value, setValue] = React.useState([options[0]]);
const [inputValue, setInputValue] = React.useState("");
return (
<div>
<div>{`value: ${value !== null ? `'${value}'` : "null"}`}</div>
<div>{`inputValue: '${inputValue}'`}</div>
<br />
<Autocomplete
multiple
value={value}
disableClearable
onChange={(event, newValue, reason) => {
if (reason !== "remove-option") {
setValue(newValue);
}
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={params => (
<TextField {...params} label="Controllable" variant="outlined" />
)}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip key={option} label={option} color="primary" />
))
}
/>
</div>
);
}
I am trying to set the selected prop with react mui menu item. I have a multiple select menu list, where I have a field allValues, that on clicking it, toggles the selection of all menu items. And that part works just fine. The code looks like this:
<Select
multiple
value={selectedValues.map(klasse => klasse.id)}
onChange={(event) => handleChange(event.target.value, onChange, idToValues)}
input={<Input id="select-multiple-chip"/>}
classes={{root: classes.select}}
renderValue={selectedIds => (
<div className={classes.chips}>
{selectedIds.map(classId => (
<Chip
key={classId}
label={idToValues[classId] && idToValues[classId].classCode}
className={classes.chip}
onDelete={(event) => onChange(selectedValues.filter(class => class.id !== classId))}/>
))}
</div>
)}
MenuProps={MenuProps}
>
{!!allValues.length &&
<MenuItem value="allValues" selected={allValues.length === selectedValues.length}>
All classes
</MenuItem>
}
{allValues.map(class => (
<MenuItem key={class.id} value={class.id}>
{class.classCode}
</MenuItem>
))}
</Select>
I can see in the dev tools that allValues and selectedValues are of the equal length, but the selected prop is still false. How is that possible, and how can I fix this?
Try adding () brackets like:
selected={(allValues.length === selectedValues.length)}