react-select add a search input inside the MenuList - reactjs

I'm using react-select (https://react-select.com) and I'm trying to add a search input inside the MenuList itself (at the top) instead of the default behaviour of searching from the Container.
<Select
...
components={{
...
MenuList: (props: any) => {
return (
<components.MenuList {...props}>
<div className="search-wrapper">
<input
value={search}
onChange={searchChange}
placeholder="Search"/>
</div>
{props.children}
</components.MenuList>
);
},
}}
>
</Select>
It's working and I get a nice input in the top of my menu:
but the input seems to be disabled. (adding disabled=false to the input doesn't effect anything)
any idea of that? why it's happens?
what is the correct way to implement such feature?

This variant works for me.
First you should remove innerProps prop from components.Menu.
I didn't try with components.MenuList, but I guess it should works too.
<components.MenuList {...props} innerProps={undefined}>
Then you will be able to use input, but menu will close on every click, so:
Second you should control menu state with menuIsOpen prop.
first you can override SelectContainer:
const SelectContainer: React.FC<CommonProps<object, false>> = ({ children, ...props }) => (
<components.SelectContainer {...props}>
<div onClick={() => setMenuOpen(true)}>
{children}
</div>
</components.SelectContainer>
);
Then add a handler to close menu on click inside menu.

Related

Two instances of the same element seem to share state

In my React application I have a Collapsible component that I use more than once, like so:
const [openFAQ, setOpenFAQ] = React.useState("");
const handleFAQClick = (question: string) => {
if (question === openFAQ) {
setOpenFAQ("");
} else {
setOpenFAQ(question);
}
};
return ({
FAQS.map(({ question, answer }, index) => (
<Collapsible
key={index}
title={question}
open={openFAQ === question}
onClick={() => handleFAQClick(question)}
>
{answer}
</Collapsible>
))
})
And the Collapsible element accepts open as a prop and does not have own state:
export const Collapsible = ({
title,
open,
children,
onClick,
...props
}: Props) => {
return (
<Container {...props}>
<Toggle open={open} />
<Title onClick={onClick}>{title}</Title>
<Content>
<InnerContent>{children}</InnerContent>
</Content>
</Container>
);
};
However, when I click on the second Collapsible, the first one opens... I can't figure out why.
A working example is available in a sandbox here.
You will need to ensure that the label and id for each checkbox is the same. Here's a working example
But if you're trying to implement an accordion style, you may need another approach.
on Collapsible.tsx line 36, the input id is set the same for both the Collapsibles. you need to make them different from each other and the problem would be solved.
One thing is that you have the same id which is wrong BUT it can still work. Just change the checkbox input from 'defaultChecked={...}' to 'checked={...}'.
The reason is that, if you use defaultSomething - it tells react that even if this value will be changed - do not change this value in the DOM - https://reactjs.org/docs/uncontrolled-components.html#default-values
Changing the value of defaultValue attribute after a component has mounted will not cause any update of the value in the DOM

Reusing Control component twice with different values

I have a custom Control component that adds an icon to one of my two selections. I need to use the Control component again but to replace the icon with a different one for my second selection but react-select doesn't work if I make a new one called Control2 for example. How would I do this?
const Control = ({ children, ...props }) => {
const style = { cursor: 'pointer'};
return (
<components.Control {...props}>
<span style={style}">
<FontAwesomeIcon icon={faSearch}/>
</span>
{children}
</components.Control>
);
};
return (
<Select
options={data}
components={Control}
isMulti
/>
// I want a new Select with new Control with different fontAwesome icon
<Select
options={data2}
components={Control}
isMulti
/>
);
I am not very clear of the complete implementation here but I think you are looking for something like this
<Select
options={data2}
components={ () => <Control faSearch="whatevericon">}
isMulti
/>
Solved by checking props for custom parent name and switching icon based off it. Simple solution example:
const Control = ({ children, ...props }) => {
const style = { cursor: 'pointer'};
let wantedIcon = "defaultIcon";
if(props.selectProps.classNamePreFix == "myControl2") {
wantedIcon = "differentIcon";
}
return (
<components.Control {...props}>
<span style={style}">
<FontAwesomeIcon icon={wantedIcon}/>
</span>
{children}
</components.Control>
);
};

How to I keep a Material-ui Select open when I click on only one of the items in it

I have been writing a custom Material-UI Select dropdown which has an optional text field at the top to allow the user to search / filter items in the Select if there were many entries.
I am struggling with how to keep the Select open when I click on the text field (rendered as an InputBase) and just have the normal behavior (of closing the Select when a regular MenuItem is selected.
CodeSandbox here : https://codesandbox.io/s/inspiring-newton-9qsyf
const searchField: TextField = props.searchable ? (
<InputBase
className={styles.searchBar}
onClick={(event: Event) => {
event.stopPropagation();
event.preventDefault();
}}
endAdornment={
<InputAdornment position="end">
<Search />
</InputAdornment>
}
/>
) : null;
return (
<FormControl>
<Select
className={styles.root}
input={<InputBase onClick={(): void => setIconOpen(!iconOpen)} />}
onBlur={(): void => setIconOpen(false)}
IconComponent={iconOpen ? ExpandMore : ExpandLess}
{...props}
>
{searchField}
{dropdownElements.map(
(currEntry: string): HTMLOptionElement => (
<MenuItem key={currEntry} value={currEntry}>
{currEntry}
</MenuItem>
)
)}
</Select>
</FormControl>
);
As you can see above I've tried using stopPropagation and preventDefault but to no avail.
check out this codesandbox link: https://codesandbox.io/s/busy-paper-9pdnu
You can use open prop of Select API
I was able to make a controlled open select by providing open prop as a react state variable and implementing correct event handlers. To make it controlled you must provide onOpen and onClose props of the Select and make sure the open prop stays true when the custom textfield is clicked.
One more important thing I had to do was override the default keyDown behavior of the Select component. If you open up a Select and start typing into it, it shifts focus to the select option that matches what you are typing. For example, if you Select had an option with the text Foobar and if you start typing Food and water, it would cause focus to shift from your custom text input onto the Foobar option. This behavior is overridden in the onKeyDown handler of the custom textfield
Working sandbox here
Edit: even though this worked in the codepen, I had to add onChange={handleOpen} to the Select as well to get it working on a real browser with React and Next.
You can still use stopPropagation to make it work
// open state
const [isSelectorOpen, setisSelectorOpen] = useState(false)
// handle change
const handleChange = event => {
const { value } = event.target
event.stopPropagation()
// set your value
}
// selector
<Select
multiple
open={isSelectorOpen}
onChange={handleChange}
input={(
<Input
onClick={() => setisSelectorOpen(!isSelectorOpen)}
/>
)}
// other attribute
>
<MenuItem>a</MenuItem>
<MenuItem>b</MenuItem>
<MenuItem>c</MenuItem>
</Select>
In my case, none of the above worked, but this did the trick to stop closing the Select:
<MenuItem
onClickCapture={(e) => {
e.stopPropagation();
}}>
You can also change onMouseEnter to change some default styling that comes with using MenuItem (like pointer cursor when mouse enters its layout)
onMouseEnter={(e) => {
e.target.style.backgroundColor = "#ffffff";
e.target.style.cursor = "default";
}}
In my case i also needed to remove the grey clicking effect that MenuItem makes on click, which is a new object generated in MuiTouchRipple-root, so changing display to none did the trick.
sx={{
"& .MuiTouchRipple-root": {
display: "none",
},
}}

React - Setting state to target with onClick method

I am trying to recreate a tabs component in React that someone gave me and I am getting stuck while getting the onClick method to identify the target.
These are the snippets of my code that I believe are relevant to the problem.
If I hardcode setState within the method, it sets it appropriately, so the onClick method is running, I am just unsure of how to set the tab I am clicking to be the thing I set the state to.
On my App page:
changeSelected = (event) => {
// event.preventDefault();
this.setState({
selected: event.target.value
})
console.log(event.target.value)
};
<Tabs tabs={this.state.tabs} selectedTab={this.state.selected}
selectTabHandler={() => this.changeSelected}/>
On my Tabs page:
{props.tabs.map(tab => {
return <Tab selectTabHandler={() => props.selectTabHandler()} selectedTab={props.selectedTab} tab={tab} />
})}
On my Tab page:
return (
<div
className={'tab active-tab'}
onClick={props.selectTabHandler(props.tab)}
>
{props.tab}
</div>
When I console.log(props.tab) or console.log(event.target.value) I am receiving "undefined"
There are a few issues causing this to happen. The first issue is that you wouldn't use event.target.value in the Content component because you aren't reacting to DOM click event directly from an onClick handler as you are in Tab, instead you are handling an event from child component. Also keep in mind that event.target.value would only be applicable to input or similar HTML elements that have a value property. An element such as <div> or a <span> would not have a value property.
The next issues are that you aren't passing the tab value from Tabs to Content and then from within Content to it's changeSelected() handler for selectTabHandler events.
In addition the onClick syntax in Tab, onClick={props.selectTabHandler(props.tab)} is not valid, you will not be able to execute the handler coming from props and pass the props.tab value. You could instead try something like onClick={() => props.selectTabHandler(props.tab)}.
Content - need to pass tab value coming from child to changeSelected():
render() {
return (
<div className="content-container">
<Tabs
tabs={this.state.tabs}
selectedTab={this.state.selected}
selectTabHandler={tab => this.changeSelected(tab)}
/>
<Cards cards={this.filterCards()} />
</div>
);
}
Tabs - need to pass tab coming from child to selectTabHandler():
const Tabs = props => {
return (
<div className="tabs">
<div className="topics">
<span className="title">TRENDING TOPICS:</span>
{props.tabs.map(tab => {
return (
<Tab
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
);
})}
</div>
</div>
);
};
export default Tabs;
Also don't forget the unique key property when rendering an array/list of items:
<Tab
key={tab}
selectTabHandler={tab => props.selectTabHandler(tab)}
selectedTab={props.selectedTab}
tab={tab}
/>
Here is a forked CodeSandbox demonstrating the functionality.

react-select How to hide dropdown menu programmatically when 'no results found' is shown?

Github Repo: react-select
After searching in the select box:
After typing a text that is not in the dropdown and enter is pressed. I want to hide the dropdown box.
My implementation:
<Select
ref={ input => this.input = input }
name="form-field-name"
searchable
autoBlur
clearable={false}
openOnFocus
onInputKeyDown={this.onInputKeyDown.bind(this)}
value={this.state.selectedOption}
onChange={this.handleChange.bind(this)}
options={this.props.items}
/>
using onInputKeyDown I am detecting enter keycode. What do I do to remove the dropdown there when 'No results found' is shown?
onInputKeyDown(e) {
if (e.keyCode === keys.ENTER) {
console.log('on input key down');
// How to detect 'No results found' shown?
// And then, how to close the dropdown?
}
}
In V2 you can achieve this by setting noOptionsMessage to a function that returns null:
<Select noOptionsMessage={() => null}/>
This will prevent the fallback option from displaying completely. Note that setting noOptionsMessage to null directly will result in an error, the expected prop type here is a function.
First method:
Turn off <Menu /> component to hide dropdown list.
<Select
components={{
...components,
Menu: () => null
}}
/>
Second method:
Turn off dropdown conditionally. e.g. When there is no value in input.
// Select.js
import { Menu } from './Menu'
<Select
{...props}
components={{
Menu
}}
/>
-------
// Menu.js
import { components } from 'react-select'
export const Menu = props => {
if (props.selectProps.inputValue.length === 0) return null
return (
<>
<components.Menu {...props} />
</>
)
}
Try using the noResultsText prop. Set it to null whenever you would want to hide it.
If you want to hide the menu when no more options are available you can try to override the MenuList component. This worked for me:
const MenuList = ({ children, ...props }: MenuListProps) => {
return Array.isArray(children) && children?.length > 0 ? (
<components.MenuList {...props}>
{children}
</components.MenuList>
) : null;
};

Resources