I'm trying to use the react-select component as an input and a select component.
Doing so I would like to prevent the menu to open while the user is typing the input.
I just can't find a way to update this behavior by either a prop or updating the onInputChange method.
My problem if I decide to use a controlled state with the menuIsOpen prop is that I can't manage to reopen the Menu control is clicked.
Here is what I have so far, do you guys any idea of how this could be achieved ?
<StyledSelect
components={{ IndicatorSeparator }}
{..._.omit(this.props, [])}
filterOption={() => true}
inputValue={inputValue}
onChange={value => {
this.select.setState({ menuIsOpen: false });
this.setState({ selectValue: value });
}}
onInputChange={(value, event) => {
if (event && event.action === 'input-change') {
this.setState({ inputValue: value });
}
}}
openMenuOnClick={false}
/>
Example
I think you are in the right direction using onInputChange and I would add a controlled menuIsOpen props like the following code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
menuIsOpen: false
};
}
openMenu = () => {
this.setState({ menuIsOpen: !this.state.menuIsOpen });
};
onInputChange = (props, { action }) => {
if (action === "menu-close") this.setState({ menuIsOpen: false });
};
render() {
const DropdownIndicator = props => {
return (
components.DropdownIndicator && (
<div
onClick={this.openMenu}
style={{ display: "flex", alignItems: "center" }}
>
<span style={{ marginLeft: 12 }}>kg</span>
<components.DropdownIndicator
{...props}
onClick={() => {
console.log("ici");
}}
/>
</div>
)
);
};
return (
<Select
components={{ DropdownIndicator }}
options={options}
onChange={this.onChange}
onInputChange={this.onInputChange}
menuIsOpen={this.state.menuIsOpen}
/>
);
}
}
The idea with this code is to fire an onClick event on the DropdownIndicator custom component.
Here a live example.
Related
I'm not able to put my fetch data as a defaultValue in my Dropdown (react-select) and Checkboxe? I have the displayed dropdown ("Good",...) but the defaultValue for the dishId:1 is Medium, so I'm supposed to see Medium already selected in my Dropdown, which is not the case (same issue for comment).
export default function MenuItemDisplay() {
...
const [dish, setDish] = useState([])
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes [0])
})
.catch(err => {
console.log(err)
})
}, [dishId]);
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
const COMMENT = [
{ label: "0", value: "0" },
...
];
function Checkbox({ value }) {
const [checked, setChecked] = React.useState(true);
return (
<label>
<input
type="checkbox"
defaultChecked={checked}
onChange={() => setChecked(!checked)}
/>
{value}
</label>
);
}
return (
<>
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti={true}
/>
<Dropdown
style={styles.select}
options={COMMENT}
defaultValue={TASTE.find((t) => t.label === dish.comment)}
isMulti={true}
/>
<Checkbox value={!!dish.trust} />
{(dish.menu) !== "") ?
<div>
Menu
<div >
{dish.menu}
</div>
</div>
: ""
}
</>
);
}
export default function CustomDropdown({
className,
style,
options,
styleSelect,
value,
setValue,
isMulti = false
}) {
const styles = {
select: {
width: "100%",
maxWidth: 200
}
};
function changeSelect(option) {
setValue(option.value);
}
return (
<div style={style} onClick={(e) => e.preventDefault()}>
{value && isMulti === false ? (
<Tag
selected={value}
setSelected={setValue}
styleSelect={styleSelect}
/>
) : (
<Select
className={className}
style={styles.select}
value={value}
onChange={changeSelect}
options={options}
isMulti={isMulti}
/>
)}
</div>
);
}
export default function Tag({ selected, setSelected, styleSelect }) {
const backgroundColor = styleSelect?.(selected?.label) ?? "grey";
return (
<div style={{
display: "flex",
justifyContent: "space-around",
padding: "0.1em",
backgroundColor: backgroundColor,
borderRadius: "4px",
color: "white",
marginRight: "20px",
marginLeft: "4px",
marginBottom: "8px"
}}>
{selected}
<button
style={{...}}
onClick={() => setSelected(null)}
>x</button>
</div>
)
}
I really don't understand why is it not working since dish.taste returns me "Medium", dish.comment returns me 5, dish.trust give me 1.
What's wrong with my Dropdown and/or Tag component?
Here there should be the main problem:
<Dropdown
defaultValue={TASTE.find((t) => t.label === dish.taste)}
/>
With this defaultValue prop you are only setting the value once, when the dropdown renders. The dish.taste can not be defined at that time if you retrieve its value through an async request. What you should do instead is adding a useEffect that sets the right value of the dropdown only after it has defined the dish.taste value. So:
const [valueDropdown, setValueDropdown] = React.useState(null);
useEffect(() => {
// here you are running this useEffect each time the dish state changes
if(dish?.taste){ //checks if taste exists
setValueDropdown(TASTE.find((t) => t.label === dish.taste));
}
}, [dish]);
Now, instead of passing to the dropdown the defaultValue you should pass two new props: the value and the setValue:
<Dropdown
value={valueDropdown}
setValue={setValueDropdown}
/>
Lastly, with the change of the props you should also update them inside the Dropdown component and remove the state definition inside that component:
export default function CustomDropdown({
style,
options,
styleSelect,
value : selected,
setValue : setSelected
}) {
// remove the line const [selected, setSelected] = useState(defaultValue);
....other logic
}
A possible fix:
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.find((d) => d.id === dishId))}
isMulti={true}
/>
The other part I could not undestand what you're trying to do, but it is wrong. As Cristiano replyied, you are comparing different types using !==, so it will never be a true condition. Maybe use the same fix as above?
(dish.find((d) => d.id === dishId).menu !== "")
EDIT AND REEDIT
Given more information, this could fix:
export default function MenuItemDisplay() {
...
const [dish, setDish] = useState(null)
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
// make your dish variable not array
setDish(res.data.dishes[0])
})
.catch(err => {
console.log(err)
})
}, [dishId]);
const TASTE = [
{ label: "Good", value: "Good" },
{ label: "Medium", value: "Medium" },
{ label: "Bad", value: "Bad" }
];
if (!dish) {
return (
<div>Loading</div>
);
}
return (
<>
<Dropdown
style={styles.select}
options={TASTE}
// remove map cuz it is not array
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti={true}
/>
{/* remove map cuz it is not array */}
{dish.menu !== "") ?
<div>
Menu
<div >
{question.map((e) => e.menu)}
</div>
</div>
: ""
}
</>
);
}
You are comparing a string (t.label) using === operator with array (result of dish.map) in defaultValue definition.
Try to change it.
Issue
I think the issue is simply that that defaultValue prop of a React component is expected to exist when the component mounts and never change. It's the default value when the component mounts. The code in your example is comparing an element in the TASTE array against the initial dish state, which is an array but accessed as an object. dish.taste of [] is undefined.
Solutions
Uncontrolled Component
If you want the Dropdown component to remain an uncontrolled component then it should be conditionally rendered only when the dish state has been initialized by the useEffect hook and the POST request.
const [dish, setDish] = useState(); // initially undefined
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes[0]);
})
.catch(err => {
console.log(err);
});
}, [dishId]);
...
{dish.taste && ( // conditionally render the dropdown
<Dropdown
style={styles.select}
options={TASTE}
defaultValue={TASTE.find((t) => t.label === dish.taste)}
isMulti
/>
)}
Controlled Component
If you want the Dropdown component to be controlled, then use the (likely) value prop instead. It's meant to be changed during the life of the component.
const [dish, setDish] = useState(); // initially undefined
useEffect(() => {
axios.post(url, { dishId })
.then(res => {
console.log(res)
setDish(res.data.dishes[0]);
})
.catch(err => {
console.log(err);
});
}, [dishId]);
...
<Dropdown
style={styles.select}
options={TASTE}
value={TASTE.find((t) => t.label === dish.taste)} // use value prop
isMulti
/>
I am trying to pass a parameter to a url which is provided by the on click event ,and use it to route to a different page.
Places autocomplete returns a list of suggested places and a event listener is attached to it such that whenever a user clicks on it ,It will direct the user to a new Page.
I have tried attaching Link from react-router to redirect the current page to the new location but I am unable to pass the latitude and longitude as parameter
class Searchbar extends React.Component {
constructor(props) {
super(props);
this.state = { address: '' };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => console.log('Success', latLng))
.catch(error => console.error('Error', error));
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input
{...getInputProps({
placeholder: 'Search Places ...',
className: 'location-search-input',
})}
/>
<div className="autocomplete-dropdown-container">
{suggestions.map(suggestion => {
const className = suggestion.active
? 'suggestion-item--active'
: 'suggestion-item';
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<Link to=`/${pass latitude and long here}`> <span>{suggestion.description}</span></Link> {//Not sure what to do here}
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
export default Searchbar
Instead if using Link, redirect user whenever PlacesAutocomplete got onSelect event
import { withRouter } from 'react-router-dom'
//...
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => this.props.history.push(`/?lat=${latLng.lat}&?lat=${latLng.lng}`))
.catch(error => console.error('Error', error));
};
//...
export default withRouter(Searchbar)
Maybe you need to add loading indicator while geocodeByAddress still working
As described in the official documentation for react-select, I'm trying to use ref and focus() to manually set the focus into the control input field. In most instances it works, but not immediately after selecting an Option from the dropdown.
After selecting an option from the dropdown, the control gets the focus but the cursor doesn't appear. It only appears if you start typing (including hitting the Esc key). On subsequent openings of the menu, the cursor appears along with the focus of the entire control field. Any ideas how to get this working?
I've created a sample code in codesandbox.io here
This is the code:
import React, { Component } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import styled from "styled-components";
import { stateOptions } from "./data.js";
class PopoutExample extends Component {
selectRef = React.createRef();
state = {
isOpen: false,
option: undefined,
};
toggleOpen = () => {
const isOpening = !this.state.isOpen;
this.setState(
{
isOpen: isOpening,
},
() => isOpening && setTimeout(() => this.selectRef.focus(), 400),
);
};
onSelectChange = option => {
this.toggleOpen();
this.setState({ option });
};
render() {
const { isOpen, option } = this.state;
return (
<Dropdown
target={
<MainButton onClick={this.toggleOpen}>
{option ? option.label : "Select a State"}
</MainButton>
}
>
<Select
menuIsOpen
ref={ref => {
this.selectRef = ref;
}}
styles={{
container: provided => ({
...provided,
display: isOpen ? "block" : "none",
}),
}}
onChange={this.onSelectChange}
options={stateOptions}
value={option}
controlShouldRenderValue={false}
/>
</Dropdown>
);
}
}
const MainButton = styled.button`
padding: 10px;
background-color: aqua;
width: 100%;
`;
const Dropdown = ({ children, target }) => (
<div>
{target}
{children}
</div>
);
ReactDOM.render(<PopoutExample />, document.getElementById("root"));
You can notice that the bug also exists in the official react-select examples. Even clicking on the blur button after the selection is not solving the problem.
There's probably a small different in the code when user closes the menu and saves + automatically closes action.
I saw you've opened an issue on github. Let's keep an eye on it.
If I can offer an alternative to the behaviour you're trying to achieve, instead of hiding the Select with css why don't just mount / unmount it ?
class PopoutExample extends Component {
state = {
isOpen: false,
option: undefined
};
toggleOpen = () => {
this.setState({
isOpen: !this.state.isOpen
});
};
onSelectChange = option => {
this.setState({ option, isOpen: !this.state.isOpen });
};
render() {
const { isOpen, option } = this.state;
return (
<Dropdown
target={
<MainButton onClick={this.toggleOpen}>
{option ? option.label : "Select a State"}
</MainButton>
}
>
{isOpen && (
<Select
autoFocus
menuIsOpen
onChange={this.onSelectChange}
options={stateOptions}
value={option}
controlShouldRenderValue={false}
/>
)}
</Dropdown>
);
}
}
Here a live example of my solution.
I want to check only the clicked Switch, but if i click on a Switch, all Switches are toggle.
class LoadMeister extends Component {
constructor(props) {
super(props);
this.state = {
daten: [],
isLoading: true,
checked: false
};
this.userUpdate = this.userUpdate.bind(this);
}
userUpdate(checked) {
this.setState({ checked });
}
[...]
render() {
const ListeUser = this.state.daten.map(meister => (
<Col md={2} xs={3} sm={3} className="karten">
<h3
title={meister.SD_Vorname}
style={{
fontWeight: "bold",
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap"
}}
>
{meister.SD_Vorname} {meister.SD_Nachname}
</h3>
<hr />
<p />
<FaBeer size="2em" />
<FaBeer size="2em" style={{ float: "right" }} />
<div style={{ marginTop: "10px" }}>
<Switch
key={meister.SD_Emplid}
width={151}
onChange={this.userUpdate}
checked={this.state.checked}
offColor="#A5C9D7"
onColor="#00556A"
/>
</div>
</Col>
));
[Return()]
xxxxxxxxxxxxxxxxxxxxxxxxxx
So what i have to do, so that only the clicked Switch is Toggle ?
I use the react-switch Lib.
xxxxxxxxxxxxxxxxxxxxxxxxxx
Try something like:
userUpdate(checkedId) {
this.setState({ checkedId:id });
}
...rest of your code
<Switch
key={meister.SD_Emplid}
width={151}
onChange={()=>this.userUpdate(meister.SD_Emplid)}
checked={this.state.checkedId===meister.SD_Emplid}
offColor="#A5C9D7"
onColor="#00556A"
/>
Yes indeed, all switches are toggled because you have just one value in your state for all switches : this.state.checked.
You need to make one value for each switch, you can use an array or an object for this :
With an array
// constructor
this.state = {
checked: [] // all switches
};
// Update the checked state at the right index
userUpdate(checked, index) {
const newChecked = this.state.checked
newChecked[index] = checked
this.setState({ checked: newChecked });
}
// render
const ListeUser = this.state.daten.map((meister,index) => (
// More code
<Switch
key={meister.SD_Emplid}
width={151}
onChange={(checked) => this.userUpdate(checked, index)} // We need to know which index is clicked
checked={this.state.checked[index]}
offColor="#A5C9D7"
onColor="#00556A"
/>
)
With an object:
// constructor
this.state = {
checked: {} // all switches, keys will be the daten ids
};
// Update the checked state
userUpdate(checked, id) {
const newChecked = this.state.checked
newChecked[id] = checked
this.setState({ checked: newChecked });
}
// render
const ListeUser = this.state.daten.map(meister => (
// More code
<Switch
key={meister.SD_Emplid}
width={151}
onChange={(checked) => this.userUpdate(checked, meister.SD_Emplid)}
checked={this.state.checked[meister.SD_Emplid]}
offColor="#A5C9D7"
onColor="#00556A"
/>
)
The two solutions are very similar.
You use the checked state for all of your Switch components, which results in the behaviour you experience.
A solution would be to save checked in your meister objects:
userUpdate(index, checked) {
const { daten } = this.state;
daten[index].checked = checked;
this.setState({ daten });
}
/* inside your render */
this.state.daten.map((meister, index) => (
/* ... */
<Switch
{/* ... */}
checked={meister.checked}
onChange={this.userUpdate.bind(this, index)}
/>
));
I use the second argument of .bind to add index as the first argument to the userUpdate function.
I am trying to use react-select. I have certain condition (boolean), when the param is true, some property/attribute of the react-select would change base on the logic. One if them is menuList. My objective, if the param is true, I want the menuList displayed and searchable, but when false, I want the menuList hidden but still available (not disabled, thats why I use onChange and onInputChange prop). Here is what I've set so far:
const isExist = true;
return (
<div style={{ width: '50%', margin: 20 }}>
<Select
id="asd"
value={selectedOption}
onChange={isExist ? this.handleChange : null}
onInputChange={isExist ? null : e => this.tests(e) }
options={options}
isClearable={true}
styles={style}
placeholder="Please type"
noOptionsMessage={() => isExist ? 'Zero Result' : null}
components={{ MenuList: () => isExist ? 'display the menu but how?' : null }}
/>
</div>
);
any help would be helpful. Thank you!
Based on the behaviour you describe you could use a controlled menuIsOpen props like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
isExist: false,
menuIsOpen: false,
selectedOption: null
};
}
onChange = e => {
this.setState({
selectedOption: e
});
};
onInputChange = (options, { action }) => {
if (this.state.isExist) {
if (action === "menu-close") {
this.setState({ menuIsOpen: false });
}
} else {
if (action === "input-change") {
// do whatever you want with the entered value
}
}
};
onFocus = e => {
if (this.state.isExist) this.setState({ menuIsOpen: true });
};
render() {
return (
<div style={{ width: "50%", margin: 20 }}>
<Select
id="asd"
value={this.state.selectedOption}
onFocus={this.onFocus}
onChange={this.onChange}
onInputChange={this.onInputChange}
options={options}
isClearable={true}
placeholder="Please type"
noOptionsMessage={() => (this.state.isExist ? "Zero Result" : null)}
menuIsOpen={this.state.menuIsOpen}
/>
</div>
);
}
}
Here a live example.