No matter what I try, I can't seem to get true Placeholder functionality for the Material-UI Select component. Using a placeholder prop on <Select />
did not work, and it looks like it's either not passing that prop to the input, or it's not displaying it somehow.
Reading through the Material-UI demo code for the Select component, it appears they achieve Placeholder functionality by using <InputLabel />
. They have this cheeky little animation that moves it to the upper left of the input. Ok cool, it's a nice effect. But it doesn't fit with the design of our site and I'd like to disable that. Unfortunately, the disableAnimation
prop just causes the label to jump to the upper left, not to disappear entirely.
What I'd like is for my dropdown component to say "Select Item" as a placeholder text, then when the menu is open, that text should be gone. It only returns if the user clicks out of the dropdown without selecting anything. If they select a value, the value should replace the placeholder and "Select Item" shouldn't appear anywhere.
(NOTE: I had this working fine when using react-select, but I need to use Material-UI's ripple effect, so I tried overriding their MenuList and MenuItem components with Material UI's, but it passed all the props down to the DOM elements and threw a bunch of errors. So I went back to the drawing board and decided to use the entire Material UI Select component.)
Current code:
const Dropdown = ({options, ...props}) => {
const [selected, setSelected] = useState('');
const testOptions = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
const handleChange = (selected) => {
setSelected(selected);
console.log('is anything happening')
}
options = options || testOptions;
const menuItems = options.map((option, index) => (
<StyledMenuItem
key={index}
value={option.value}
label={option.label}
/>
));
return (
<FormControl hiddenLabel>
<InputLabel disableAnimation>Select Item</InputLabel>
<StyledSelect
value={selected}
onChange={handleChange}
variant="outlined"
disableUnderline={true}
>
<MenuItem value="">
<em>Select Item</em>
</MenuItem>
{menuItems}
</StyledSelect>
</FormControl>
)
};
const StyledMenuItem = styled(MenuItem)`
min-height: 32px;
height: 32px;
padding: 0 12px;
font-family: 'Work Sans', sans-serif;
font-weight: 400;
font-size: 17px;
line-height: 20px;
color: ${colors.primary800};
outline: none;
&:hover {
background-color: ${colors.primary100};
}
& .MuiTouchRipple-root {
color: ${colors.primary050};
}
`
const StyledSelect = styled(Select)`
input::-webkit-contacts-auto-fill-button,
input::-webkit-credentials-auto-fill-button {
display: none !important;
}
border: 1px solid ${colors.primary400};
border-radius: 2px;
height: 40px;
box-shadow: none;
outline: none;
& .MuiSelect-icon {
fill: ${colors.primary300};
width: 36px;
height: 36px;
top: inherit;
}
`
I couldn't find a really clean way, but the following seems to do the trick:
<InputLabel shrink={false}>
{selected !== '' && 'Select item'}
</InputLabel>
Adding the shrink={false}
makes sure the label doesn't move up when focussed. With the default Material-UI styling the options will be over the InputLabel, so you won't see it when selecting. Then, when a value is selected the selected
variable will be set and the text will hide from the label.
If the select items don't appear over the InputLabel because of your custom styling you can maybe track the focus with onFocus
and onBlur
to hide the label content when the select is focussed.