I'm trying to create a custom select using React-Select options. I would like to have my search not in the control box, but rather in the menu. I tried this:
import React from "react";
import Select, { components } from "react-select";
import { colourOptions, groupedOptions } from "./docs/data";
const MenuList = props => {
return (
<components.MenuList {...props}>
<components.Input {...props} />;
{props.selectProps.inputValue.length > 1 ? props.children : ""}
</components.MenuList>
);
};
export default () => (
<Select
defaultValue={colourOptions[1]}
options={groupedOptions}
components={{ MenuList }}
/>
);
Problem is I'm getting an error saying
Uncaught Invariant Violation: input is a void element tag and must neither have children nor use dangerouslySetInnerHTML
I'm guessing the react select's components.Input
is rendering another div inside the input
tag or something like that.
Does anybody have an idea how this can be done maybe?
You should inspire yourself with what's suggested in the documentation in the section Advanced
here: https://react-select.com/advanced.
I have recreated a live example in CodeSandbox so you can see it in action and play with it. But the main idea is to embed the original Select
element inside some controlled element and them edit the style of your Select
to make it feel as one single MenuList
.
class PopoutExample extends Component<*, State> {
state = { isOpen: false, value: undefined };
toggleOpen = () => {
this.setState(state => ({ isOpen: !state.isOpen }));
};
onSelectChange = value => {
this.toggleOpen();
this.setState({ value });
};
render() {
const { isOpen, value } = this.state;
return (
<Dropdown
isOpen={isOpen}
onClose={this.toggleOpen}
target={
<Button
iconAfter={<ChevronDown />}
onClick={this.toggleOpen}
isSelected={isOpen}
>
{value ? `State: ${value.label}` : "Select a State"}
</Button>
}
>
<Select
autoFocus
backspaceRemovesValue={false}
components={{ DropdownIndicator, IndicatorSeparator: null }}
controlShouldRenderValue={false}
hideSelectedOptions={false}
isClearable={false}
menuIsOpen
onChange={this.onSelectChange}
options={stateOptions}
placeholder="Search..."
styles={selectStyles}
tabSelectsValue={false}
value={value}
/>
</Dropdown>
);
}
}
// styled components
const Menu = props => {
const shadow = "hsla(218, 50%, 10%, 0.1)";
return (
<div
css={{
backgroundColor: "white",
borderRadius: 4,
boxShadow: `0 0 0 1px ${shadow}, 0 4px 11px ${shadow}`,
marginTop: 8,
position: "absolute",
zIndex: 2
}}
{...props}
/>
);
};
const Blanket = props => (
<div
css={{
bottom: 0,
left: 0,
top: 0,
right: 0,
position: "fixed",
zIndex: 1
}}
{...props}
/>
);
const Dropdown = ({ children, isOpen, target, onClose }) => (
<div css={{ position: "relative" }}>
{target}
{isOpen ? <Menu>{children}</Menu> : null}
{isOpen ? <Blanket onClick={onClose} /> : null}
</div>
);
const Svg = p => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
focusable="false"
role="presentation"
{...p}
/>
);
const DropdownIndicator = () => (
<div css={{ color: colors.neutral20, height: 24, width: 32 }}>
<Svg>
<path
d="M16.436 15.085l3.94 4.01a1 1 0 0 1-1.425 1.402l-3.938-4.006a7.5 7.5 0 1 1 1.423-1.406zM10.5 16a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11z"
fill="currentColor"
fillRule="evenodd"
/>
</Svg>
</div>
);
const ChevronDown = () => (
<Svg style={{ marginRight: -6 }}>
<path
d="M8.292 10.293a1.009 1.009 0 0 0 0 1.419l2.939 2.965c.218.215.5.322.779.322s.556-.107.769-.322l2.93-2.955a1.01 1.01 0 0 0 0-1.419.987.987 0 0 0-1.406 0l-2.298 2.317-2.307-2.327a.99.99 0 0 0-1.406 0z"
fill="currentColor"
fillRule="evenodd"
/>
</Svg>
);
It is inspired by https://codesandbox.io/embed/m75wlyx3oy solution.
Default MenuList
is exchanged with CustomMenuWithInput
which in first place show options in dropdown. Options list is provided by { props.children }
. Under last option comes input field.
onFocus={onMenuInputFocus}
prevents from closing dropdown on each letter typed in input.
const CustomMenuWithInput = ({ selectProps: { onMenuInputFocus }, ...props }) => {
const [value, setValue] = useState('')
return (
<div>
{ props.children }
<input
id='add-option'
label='Add option'
placeholder='option'
autoCorrect='off'
autoComplete='off'
spellCheck='false'
type='text'
value={value}
onChange={(e) => setValue(e.target.value)}
onMouseDown={(e) => {
e.stopPropagation()
e.target.focus()
}}
onFocus={onMenuInputFocus}
/>
</div>
)
}
const SelectView = ({ onChange }) => {
const [inputValue, setInputValue] = useState('')
const [isFocused, setIsFocused] = useState(false)
return (
<Select
id=''
label={'List'}
options={list}
onChange={opt => {
onChange(opt.value, opt)
setIsFocused(false)
}}
onInputChange={setInputValue}
onMenuInputFocus={() => setIsFocused(true)}
inputValue={inputValue}
placeholder='Select...'
components={{
MenuList: CustomMenuWithInput,
}}
isSearchable
{...{
menuIsOpen: isFocused || undefined,
isFocused: isFocused || undefined
}}
/>
)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With