'Search input as option in Material UI Select component
Solution 1:[1]
Problem Explanation
This happens because MenuItem is a direct child of the Select component. It acts in the same way as described in Selected menu documentation. You can see that when Menu (or in our case Select) is opened, focus is automatically put onto the selected MenuItem. That's why TextField loses focus in the first place (even if we attach an autoFocus attribute to it).
Ok, so we can obviously simply hack this using the useRef hook on a TextField and then call the focus() method when Select opens. Something like this:
var inputRef = useRef(undefined);
...
<Select onAnimationEnd={() -> inputRef.current.focus()} ... >
...
<TextField ref={inputRef} ... />
...
</Select>
Well not so fast, there's a catch!
Here's where things break: You open your select and focus shifts to the input field as expected. Then you insert search text that would filter out the currently selected value. Now if you remove text with backspace until the currently selected item appears again, you would see that focus would automatically be placed from input field to the currently selected option. It might seem like a viable solution at first but you can see where UX suffers and we don't get expected stable behavior that we want.
Solution
I find your question a bit vague without any code and proper explanation, but I still find it useful since I was searching for the exact same solution as you: Searchable Select MUI Component with good UX. So please note that this is my assumption of what you wanted as a working solution.
Here's a CodeSandbox with my working solution. All the important bits are explained in code comments and here:
- The most important change is adding
MenuProps={{ autoFocus: false }}attribute to theSelectcomponent andautoFocusattribute to theTextFieldcomponent. - I've put the search
TextFieldinto aListSubheaderso that it doesn't act as a selectable item in the menu. i.e. we can click the search input without triggering any selection. - I've added a custom
renderValuefunction. This prevents rendering empty string in Select's value field if search text would exclude the currently selected option. - I've disabled event propagation on
TextFieldwith theonKeyDownfunction. This prevents auto selecting item while typing (which is defaultSelectbehavior) - [BONUS] You can easily extend this component with multi selection support, which was what I ended up making for my project, but I'll not include the details here since it's out of the scope of your question.
Solution 2:[2]
The MUI Autocomplete component might be just what you need [https://mui.com/components/autocomplete/](MUI Autocomplete)
Solution 3:[3]
You can place <ListSubheader><TextField> these two components inside <Select> component something below.
output:
app.js
import React, { useState, useMemo } from "react";
import Checkbox from "@material-ui/core/Checkbox";
import InputLabel from "@material-ui/core/InputLabel";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import MenuItem from "@material-ui/core/MenuItem";
import FormControl from "@material-ui/core/FormControl";
import Select from "@material-ui/core/Select";
import ListSubheader from "@material-ui/core/ListSubheader";
import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import { MenuProps, useStyles, options } from "./utils";
const containsText = (text, searchText) =>
text.toLowerCase().indexOf(searchText.toLowerCase()) > -1;
function App() {
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [searchText, setSearchText] = useState("");
const isAllSelected =
options.length > 0 && selected?.length === options.length;
const displayedOptions = useMemo(
() => options.filter((option) => containsText(option, searchText)),
[searchText]
);
const handleChange = (event) => {
console.log("vals", event.target);
const value = event.target.value;
if (value[value.length - 1] === "all") {
setSelected(selected?.length === options.length ? [] : options);
return;
}
setSelected(value);
console.log("values", selected);
};
return (
<FormControl className={classes.formControl}>
<InputLabel id="mutiple-select-label">Multiple Select</InputLabel>
<Select
labelId="mutiple-select-label"
multiple
variant="outlined"
value={selected || []}
onChange={handleChange}
renderValue={(selected) => selected}
MenuProps={MenuProps}
onClose={() => setSearchText("")}
>
<ListSubheader>
<TextField
size="small"
// Autofocus on textfield
autoFocus
placeholder="Type to search..."
fullWidth
InputProps={{
startAdornment: <InputAdornment position="start"></InputAdornment>
}}
onChange={(e) => setSearchText(e.target.value)}
onKeyDown={(e) => {
if (e.key !== "Escape") {
// Prevents autoselecting item while typing (default Select behaviour)
e.stopPropagation();
}
}}
/>
</ListSubheader>
<MenuItem
value="all"
classes={{
root: isAllSelected ? classes.selectedAll : ""
}}
>
<ListItemIcon>
<Checkbox
classes={{ indeterminate: classes.indeterminateColor }}
checked={isAllSelected}
indeterminate={
selected?.length > 0 && selected.length < options.length
}
/>
</ListItemIcon>
<ListItemText
classes={{ primary: classes.selectAllText }}
primary="Select All"
/>
</MenuItem>
{displayedOptions.map((option) => (
<MenuItem key={option.id} value={option}>
<ListItemIcon>
<Checkbox checked={selected?.includes(option)} />
</ListItemIcon>
<ListItemText primary={option.title}>{option}</ListItemText>
</MenuItem>
))}
</Select>
<p>{selected}</p>
</FormControl>
);
}
export default App;
util.js
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles((theme) => ({
formControl: {
margin: theme.spacing(1),
width: 300
},
indeterminateColor: {
color: "#f50057"
},
selectAllText: {
fontWeight: 500
},
selectedAll: {
backgroundColor: "rgba(0, 0, 0, 0.08)",
"&:hover": {
backgroundColor: "rgba(0, 0, 0, 0.08)"
}
}
}));
const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 250
}
},
getContentAnchorEl: null,
anchorOrigin: {
vertical: "bottom",
horizontal: "center"
},
transformOrigin: {
vertical: "top",
horizontal: "center"
},
variant: "menu"
};
const options = [
"Oliver Hansen",
"Van Henry",
"April Tucker",
"Ralph Hubbard",
"Omar Alexander",
"Carlos Abbott",
"Miriam Wagner",
"Bradley Wilkerson",
"Virginia Andrews",
"Kelly Snyder"
];
export { useStyles, MenuProps, options };
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Greg-- |
| Solution 3 | KARTHIKEYAN.A |


