I am trying to toggle the visibility of a Dropdown based on another Dropdown's value. So when the first option is selected, the second dropdown becomes visible below and when that option is no longer selected, the dropdown below vanishes.
Please bear with me as I am still learning. Any advice or help would be appreciated! Here is what I have so far:
const firstOptions = [
{ key: 0, value: 'default', text: '--Select an Option--' },
{ key: 1, value: 'option1', text: 'option1 - when I am selected another shall appear' },
{ key: 2, value: 'option2', text: 'option2' },
{ key: 3, value: 'option3', text: 'option3' },
];
const secondOptions = [
{ key: 0, value: 'default', text: '--Select an Option--' },
{ key: 1, value: 'option1', text: 'option1' },
{ key: 2, value: 'option2', text: 'option2' },
];
class DropdownToggle extends Component {
constructor(props) {
super(props);
this.state = {
selectedValue: 'default',
};
}
handleChange = (e) => {
this.setState({ selectedValue: e.target.value });
}
render() {
const message = 'You selected ' + this.state.selectedValue;
return (
<div style={{ marginTop: '50px', marginLeft: '50px' }}>
<Dropdown
options={firstOptions}
selection
value={this.state.selectedValue}
onChange={this.handleChange}
/>
<div style={{ marginTop: '50px' }}>
<Dropdown
options={secondOptions}
selection
/>
</div>
<p>{message}</p>
</div>
);
}
}
You can do conditional rendering to achieve desired behavior.
Example
render() {
const message = 'You selected ' + this.state.selectedValue;
return (
<div style={{ marginTop: '50px', marginLeft: '50px' }}>
<Dropdown
options={firstOptions}
selection
value={this.state.selectedValue}
onChange={this.handleChange}
/>
{
this.state.selectedValue === 'option1' ?
<div style={{ marginTop: '50px' }}>
<Dropdown
options={secondOptions}
selection
/>
</div>
: null
}
<p>{message}</p>
</div>
);
}
Update
Your handleChange function is not correct. According to Semantic-UI docs onChange fires with 2 parameters. event and data. If you change your function like below it should work.
onChange {func}
Called when the user attempts to change the value.
onChange(event: SyntheticEvent, data: object)
event React's original SyntheticEvent.
data All props and proposed value.
handleChange = (event, data) => {
console.log(data.value);
this.setState({selectedValue: data.value});
}
My solution uses React Hooks and I'm utilising state from the context of a React.FunctionComponent<ITypeProps> and detecting the current element in a generated dropdown to set the state (hide/show an element by adding Bootstrap style classes).
For simplicity of logic I am depicting a non-generated dropdown list & it's associated onChange event.
Hooks let you use React features (like state) from a function — by doing a single function call.
As per Making Sense of React Hooks.
const Manager: React.FunctionComponent<ITypeProps> = ({}) => {
const [state, setState] = React.useState({
showElement: false
});
const handleChange = (value) => {
if (value && (value == 1)
setState({ ...state, showElement: true });
else
setState({ ...state, showElement: false });
}
return
<div>
<select>
<option value="1" onChange={handleChange}>Item1 + Show Div</option>
<option value="2" onChange={handleChange}>Item2</option>
<option value="3" onChange={handleChange}>Item3</option>
</select>
<div className={state.showElement ? "d-block" : "d-none"}> Hide / Show me!</div>
</div>
}
What this will do:
On change of the select dropdown, it will update the state
If the selected value of the dropdown is "1", the boolean variable showElement will be true
If the selected value of the dropdown is anything but "1" the boolean variable showElement will be false
On change of the state the component will re-render
The visibility of the div will change with the state change to have either the style "display: block" or "display: none" as per the Bootstrap shorthand d-block and d-none.
I spent too long working out this really simple React feature.
So hopefully this simple example helps someone & saves you some time!
Related
what i am creating here is a sorter with 3 inputs like shown here . This sorter will get some data from a table. Now i'm setting state with initSelect and i'm passing it the fields array but when i console.log(select) it gives me the object shown in the image which is incorrect from the behaviours i want {sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
The first input has to have a default value of 'Date' always , but it can change to other values in the dropdown list like name , username ect . When i console log the select state it is messed up as it's always selecting the last one on the fields array , how can i change the initSelects function to correctly build the object i want.
Also the tricky thing which i can't seem to do is , if this Date value is selcted , in the second input, the date value should not be there. And if in the second input we select another value like Earth , Earth and Date should not be in the 3rd input and so on. So basically it means filtering out values . I need serious help as this is for the company i work on
Excepted Behaviour: Dynamically update value every time i select one input element like
{sorterParam1: 'Date', sorterParam2: '', sorterParam3: ''}
When selectin 'Date' for example , it shouldn't not be included in the dropdownlist on sorterParam2, sorterParam3.
/*eslint-disable*/
import React, { useState, useMemo } from 'react';
import TextField from '#mui/material/TextField';
import Button from '#mui/material/Button';
import { GridSortModel } from '#mui/x-data-grid';
import SorterField from './SorterField';
const initSelects = (fields) => {
let object = {};
fields.map((item) => {
console.log(item, 'item');
object = {
...item,
[item.name]: item.defaultValue ? item.defaultValue : '',
};
});
return object;
};
const Sorter = ({ menuItemsValue, setSortData }: SortProps) => {
const fields: SorterProps[] = [
{
name: 'sorterParam1',
title: 'Sort by',
optional: false,
defaultValue: 'Date',
},
{
name: 'sorterParam2',
title: 'Then by',
optional: true,
},
{
name: 'sorterParam3',
title: 'Then by',
optional: true,
},
];
const [select, setSelect] = useState<any>(() => initSelects(fields));
const getMenuItems = useMemo(() => {
return menuItemsValue.filter((item) => select.sorterParam1 !== item);
}, [menuItemsValue, select]);
const handleSelectChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
console.log(111, name, value);
setSelect({ ...select, [name]: value });
setSortData(sortOptions);
};
const handleClearAllInputs = () => {
setSelect({
sorterParam1: '',
sorterParam2: '',
sorterParam3: '',
});
};
const handleConfirm = () => {};
return (
<TextField
label="Advanced Sorting"
className={styles.sorter__inputs}
id="sorter-parameter-1"
variant="standard"
InputProps={{
disableUnderline: true,
}}
select
SelectProps={{
IconComponent: (props) => <NewIcon {...props} />,
}}
sx={{
fontSize: '12px',
width: '100%',
'& .MuiInputBase-input:focus': {
backgroundColor: 'transparent !important',
},
'& .MuiInputLabel-root': {
color: '#9E9E9E',
},
'& .MuiTextField-root': {
fontSize: '13px',
},
'& .MuiOutlinedInput-root': {
backgroundColor: '#fff',
},
}}
>
{fields.map((option, index) => (
<SorterField
key={option.name}
menuItemsValue={getMenuItems}
name={option.name}
option={option}
count={fields.length}
handleChange={handleSelectChange}
index={index + 1} // setData={setData}
/>
))}
<div className={styles.sorter__inputControllers}>
<Button
className={styles.sorter__clearAllInput}
onClick={() => handleClearAllInputs()}
>
Clear All
</Button>
<Button
onClick={() => handleConfirm()}
className={styles.sorter__confirmInput}
>
Confirm
</Button>
</div>
</TextField>
);
};
export default Sorter;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
This is the SorterField Component code if that might be helpful
/*eslint-disable*/
import React, { useState } from 'react';
import TextField from '#mui/material/TextField';
import { MenuItem } from '#mui/material';
import { SorterProps } from '../../types/Sorter';
import { ReactComponent as SorterLine } from '../../assets/img/sortLine.svg';
import styles from '../../assets/components/Sorter/sorter.module.scss';
type SorterFieldProps = {
menuItemsValue: string[];
option: SorterProps;
count: number;
name: string;
handleChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
index: number;
};
function SorterField({
option,
count,
menuItemsValue,
handleChange,
index,
}: SorterFieldProps) {
const handleSorting = () => {};
return (
<div className={styles.sorter__container}>
<div className={styles.sorter__header}>
<p className={styles.sorter__label}>
{option.title}{' '}
{option.optional && (
<sup className={styles.sorter__optional}>*Optional</sup>
)}
</p>
<div className={styles.sorter__numbers__container}>
{Array.from({ length: count }, (_, i) => i + 1).map((number) => (
<>
{number === index ? (
<>
<span className={styles.sorter__number}>{index}</span>
</>
) : (
<>
<span className={styles.sorter__numbers}>{number}</span>
</>
)}
</>
))}
</div>
</div>
<div className={styles.sorter__inputs}>
<TextField
className={[styles.sorter__input, styles.sorter__field__input].join(
' '
)}
variant="outlined"
label="Select"
select
SelectProps={{
IconComponent: () => <NewIcon />,
}}
value={option.defaultValue}
onChange={handleChange}
name={option.title}
size="small"
>
{menuItemsValue.map((title, idx) => (
<MenuItem key={idx} value={title}>
{title}
</MenuItem>
))}
</TextField>
<div onClick={handleSorting} className={styles.sorter__sortOrder}>
<div className={styles.sorter__sortOrder__alphabetical}>
<span>A</span>
<span>Z</span>
</div>
<div className={styles.sorter__sortOrder__sortLine}>
<SorterLine />
</div>
</div>
</div>
</div>
);
}
export default SorterField;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
You're re-assigning a value to object in each iteration of fields.map().
If you want to build object as a map of field item name to defaultValue (or blank string), use this instead...
return Object.fromEntries(
fields.map(({ name, defaultValue }) => [name, defaultValue ?? ""])
);
See Object.fromEntries()
Also, move the fields declaration outside your component. It's static content so can be omitted from being declared every render and from any hook dependencies.
You could also use fields.reduce() which is basically the same thing
return fields.reduce(
(obj, { name, defaultValue }) => ({
...obj,
[name]: defaultValue ?? "",
}),
{}
);
As for removing selected options as you iterate through the fields, that's a little trickier.
You could use a memo hook to create an iterable data structure that includes the available options for that particular iteration.
For example
const fieldsWithOptions = useMemo(() => {
const taken = new Set(Object.values(select));
const available = menuItemsValue.filter((item) => !taken.has(item));
return fields.map((field) => ({
...field,
options: select[field.name]
? [select[field.name], ...available] // include the current selection
: available,
}));
}, [menuItemsValue, select]);
Then map over fieldsWithOptions instead of fields and use option.options instead of getMenuItems.
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.
I'm using ReactJS, and shopify's polaris in order to create a website. I'm very new to react so this might be a newbie question but I looked over the internet and couldn't manage to put the pieces together.
I have a dropdown list and basically whenever the user clicks on an item from a list I want to add a button next to the dropdown. Here is my code:
import React from "react";
import { ActionList, Button, List, Popover } from "#shopify/polaris";
export default class ActionListExample extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false,
title: "Set Period",
};
}
renderButton() {
console.log("Button clicked")
return (
<div>
<Button fullWidth={true}>Add product</Button>;
</div>
);
}
togglePopover = () => {
this.setState(({ active }) => {
return { active: !active };
});
};
render() {
const activator = (
<Button onClick={this.togglePopover}>{this.state.title}</Button>
);
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One" }, function() {
this.togglePopover();
this.renderButton() //THIS IS WHERE I CALL THE METHOD
});
}
}
]}
/>
</Popover>
</div>
);
}
}
I've placed a comment in the code to show where I call the renderButton() method. Whenever I click the "One" element in the dropdown, it prints out "Button clicked" but nothing gets rendered to the screen. Any help is greatly appreciated. Thanks in advance!
You need to add another variable to check if an item is clicked, and as #azium commented, you need to add the output to your JSX, not inside the onAction function.
As of right now, you close the Popper when an item is clicked, setting this.state.active to false, so you can't rely on that to render your button. You need to add something like this.state.isButton or something and in onAction include:
onAction: () => {
this.setState({ title: "One", isButton: true }, () => {
this.togglePopover();
});
}
and then in your JSX:
{this.state.isButton && this.renderButton()}
This is a perfect use case for conditional rendering.
You basically want to render a component based on a condition (Boolean from your state in this case).
Conditional rendering can be written is several ways as you can see in the docs.
In your case i would go for something like this:
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One" }, function() {
this.togglePopover();
});
}
}
]}
/>
{this.state.active && this.renderButton()}
</Popover>
</div>
);
}
}
Note that i just placed it at a random place, feel free to move it wherever you need it in the markup.
Thanks to everyone's help I finally was able to do this. I placed an extra attribute in the state called isButton and I initially set it equal to false. Here is my render function:
render() {
const activator = (
<Button onClick={this.togglePopover}>{this.state.title}</Button>
);
return (
<div style={{ height: "250px" }}>
<Popover
active={this.state.active}
activator={activator}
onClose={this.togglePopover}
>
<ActionList
items={[
{
content: "One",
onAction: () => {
this.setState({ title: "One", isButton: true }, function() { //Set isButton to true
this.togglePopover();
});
}
}
]}
/>
{this.state.isButton && this.renderButton()} //ADDED HERE
</Popover>
</div>
);
}
Please look at the comments to see where code was changed. Thanks!
I'm calling a custom component in my redux-form.
<Field name="myField" component={SiteProjectSelect}/>
This component is a combination of two combo boxes. The second box is dependant on the value of the first on - i.e. depending on what site you select, you can choose from a list of projects. What I'd like to do is get the form to receive the selected site and the selected projects. However, I'm not sure how to pass the values to the redux-form.
class SiteProjectSelect extends Component {
constructor() {
super();
this.state = {
selectedSite: null,
selectedProject: null,
};
}
handleSiteSelection = selectedSite => {
console.log(selectedSite)
this.setState({ selectedSite, selectedProject: null });
};
handleProjectSelection = selectedProject => {
this.setState({ selectedProject });
this.props.input.onChange(selectedProject.value);
};
render() {
const selectedRow = this.state.selectedSite ? projects.find((node) => node.site === this.state.selectedSite.value) : "";
const filteredProjectOptions = selectedRow ? selectedRow.projects.map(project => ({ value: project, label: project })) : []
return (
<div {...this.props} >
<label>Site</label>
<div style={{ marginBottom: '20px' }} >
<Select
name="site"
value={this.state.selectedSite}
onChange={this.handleSiteSelection}
options={siteOptions}
isSearchable
/>
</div>
<div style={{ marginBottom: '20px' }} >
<label>Project</label>
<Select
name="project"
value={this.state.selectedProject}
onChange={this.handleProjectSelection}
options={filteredProjectOptions}
isMulti
isSearchable
closeMenuOnSelect={false}
/>
</div>
</div>
);
}
}
I did finally figure it out. For anyone else who stumbles across this, here's what I needed to know. To use a custom component,
Use the onChange prop to set the new value of the Field. You do this by calling the onChange function, this.props.input.onChange(your-components-new-value-here) when you need to change the value of the component and passing it the new value.
This new value will now be stored in the value prop: this.props.input.value. So, wherever in the render function for your component you need to pass/display the current value of your component, use the value prop. It has to be the value prop and not another variable such as what you passed to your onChange function. What this does is give control of what's displayed to the state of your redux-form which the value prop is tied to. Why is this useful? For example, you could take the user to a form review page when they're done and then back to the form if the user wants to make some more changes. How would redux-form know how to repopulate all of what's displayed without getting the user to fill in the form again? Because the display is dependant on the state, not user input! Took me a while to make sense of all this!!
In my example, where I was using two react-select components, one of which was dependant on the other, I ended up having to use the Fields component which allowed me to have two Fields in my component rather than just the one. Once I implemented this, it also became evident that I didn't need to have a separate state within my component as the value of both Fields is always accessible via the value prop for each of them. So, yes, I could have just used a stateless function after all!
I call my component with:
<Fields names={["site", "projects"]} component={SiteProjectSelect} />
My final working component:
class SiteProjectSelect extends Component {
handleSiteSelection = selectedSite => {
this.props.site.input.onChange(selectedSite);
this.props.projects.input.onChange(null);
};
handleProjectSelection = selectedProjects => {
this.props.projects.input.onChange(selectedProjects);
};
renderSite = () => {
const {
input: { value },
meta: { error, touched }
} = this.props.site;
return (
<div>
<label>Site</label>
<div style={{ marginBottom: '20px' }}>
<Select
name="site"
value={value}
onChange={this.handleSiteSelection}
options={siteOptions}
isSearchable
/>
</div>
<div className="red-text" style={{ marginBottom: '20px' }}>
{touched && error}
</div>
</div>
);
};
renderProjects = () => {
var {
input: { value },
meta: { error, touched }
} = this.props.projects;
const selectedSite = this.props.site.input.value;
const selectedRow = selectedSite
? projects.find(node => node.site === selectedSite.value)
: '';
const filteredProjectOptions = selectedRow
? selectedRow.projects.map(project => ({
value: project,
label: project
}))
: [];
return (
<div>
<div style={{ marginBottom: '20px' }}>
<label>Projects</label>
<Select
name="projects"
value={value}
onChange={this.handleProjectSelection}
options={filteredProjectOptions}
isMulti
isSearchable
closeMenuOnSelect={false}
/>
</div>
<div className="red-text" style={{ marginBottom: '20px' }}>
{touched && error}
</div>
</div>
);
};
render() {
return (
<div>
{this.renderSite()}
{this.renderProjects()}
</div>
);
}
}
I have a material-ui select box that is populated with a state variable. No matter what I have tried, I cannot get the value to actually show when I select an option. Can anyone tell me why? It keeps just giving me a blank bar. I even took an example from another code sandbox and copied it almost exactly. One thing I did notice is that my event.target.value is always undefined, and I am not sure why. So I just use value, instead, in my handleChange function. Any help is greatly appreciated! This has been driving me crazy.
Code Sandbox: https://codesandbox.io/s/jnyq16279v
Code:
import React from 'react';
import MenuItem from 'material-ui/MenuItem';
import Select from 'material-ui/SelectField';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
export default class KKSelect extends React.Component {
constructor(props) {
super(props);
this.state = {
selectOptions: [
{
value: "Image",
id: "1"
},
{
value: "Integer",
id: "2"
},
{
value: "Decimal",
id: "3"
},
{
value: "Boolean",
id: "4"
},
{
value: "Text",
id: "5"
}
],
selectedValue: ""
};
}
renderSelectOptions = () => {
return this.state.selectOptions.map((dt, i) => {
return (
<MenuItem key={i} value={dt.id}>
{dt.value}
</MenuItem>
);
});
}
handleChange = (event, value) => {
this.setState({ selectedValue: value });
};
render() {
return (
<MuiThemeProvider>
<Select
value={this.state.selectedValue}
onChange={this.handleChange}
>
{this.renderSelectOptions()}
</Select>
</MuiThemeProvider>
);
}
}
First of all, you are using material-ui version 0.20.1 - docs for that version is here: https://v0.material-ui.com/#/components/select-field, but it's recommended to use v1 (https://material-ui.com/getting-started/installation/).
For version 0.20.1, there are few problems with your code:
First: renderSelectOptions: {dt.value} should be assigned to MenuItem primaryText
renderSelectOptions = () => {
return this.state.selectOptions.map((dt, i) => {
return (
<MenuItem key={i} value={dt.id}>
{dt.value}
</MenuItem>
);
});
}
like this:
renderSelectOptions = () => {
return this.state.selectOptions.map((dt, i) => (
<MenuItem key={dt.id} value={dt.id} primaryText={dt.value} />
));
};
And second - handle change has event, index and value arguments, so your value is acctually index - not value.
handleChange = (event, value) => {
this.setState({ selectedValue: value });
};
Should be:
handleChange = (event, index, value) => {
this.setState({ selectedValue: value });
};
Check out working version for material-ui version 0.20.1 here: https://codesandbox.io/s/9q3v1746jy
P.S. If you are using material-ui version 1.2.1, I made working example for that version too, you can check it out here: https://codesandbox.io/s/jjvrnokkv3
it could be solve by using custom styling in listbox props . plz refer my code .
<Autocomplete
key={`${filterKey}-township`}
onChange={onAutocompleteChange}
disableClearable
multiple
fullWidth
limitTags={1}
value={value}
disableCloseOnSelect={true}
size="small"
id="township"
options={townships}
getOptionLabel={(option) => option.Township}
ListboxProps={{
style: {
position: "absolute",
top: 10,
bottom: 0,
right: 0,
left: 0,
height:300,
width: "100%",
overflowY: "auto",
backgroundColor: "white",
},
}}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
label="Township"
onKeyDown={handleKeyDown}
/>
)}
renderOption={(option, { selected }) => (
<React.Fragment>
<Checkbox size="small" icon={icon} checkedIcon={checkedIcon} checked={selected} />
<Typography variant="body2">{option.Township}</Typography>
</React.Fragment>
)}
/>