I have a problem where I have to render 4 dropdowns, each with similar options(i just rendered 1 here). If I select an option in any one of the dropdowns, that option should not be available in the other three.
How should I update the selected_planets in the state? The below code updates the selected_planets from 1 select dropdown. But still that same option is available everywhere and I could not able to get 4 different options inside selected_planets array? How should I proceed?
Also, the response from API fetch is an array of objects, which I mapped through and update in planets Array. For demo purpose, let's consider, planets: [Neptune, Saturn, Mars, Earth, Venus, Jupiter]
import React, { Component } from 'react';
export default class Dashboard extends Component {
state = {
planets: [],
selected_planets: []
};
ponentDidMount() {
fetch('url')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ planets: data });
});
}
handleSelect = event => {
this.setState({ selected_planets: [event.target.value] });
};
render() {
let select_Planets = this.state.planets.map(planet => {
return planet.name;
});
let options = select_Planets.map(planet => (
<option key={planet} value={planet}>
{planet}
</option>
));
return (
<select onChange={this.handleSelect}>
<option defaultChecked></option>
{options}
</select>
);
}
}
I have a problem where I have to render 4 dropdowns, each with similar options(i just rendered 1 here). If I select an option in any one of the dropdowns, that option should not be available in the other three.
How should I update the selected_planets in the state? The below code updates the selected_planets from 1 select dropdown. But still that same option is available everywhere and I could not able to get 4 different options inside selected_planets array? How should I proceed?
Also, the response from API fetch is an array of objects, which I mapped through and update in planets Array. For demo purpose, let's consider, planets: [Neptune, Saturn, Mars, Earth, Venus, Jupiter]
import React, { Component } from 'react';
export default class Dashboard extends Component {
state = {
planets: [],
selected_planets: []
};
ponentDidMount() {
fetch('url')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ planets: data });
});
}
handleSelect = event => {
this.setState({ selected_planets: [event.target.value] });
};
render() {
let select_Planets = this.state.planets.map(planet => {
return planet.name;
});
let options = select_Planets.map(planet => (
<option key={planet} value={planet}>
{planet}
</option>
));
return (
<select onChange={this.handleSelect}>
<option defaultChecked></option>
{options}
</select>
);
}
}
Share
Improve this question
edited Sep 29, 2019 at 19:38
yacine benzmane
4,1884 gold badges24 silver badges32 bronze badges
asked Sep 29, 2019 at 17:38
Deepika DeepiDeepika Deepi
1271 silver badge8 bronze badges
2
- I would treat planet and dropdown as one ponent and parent ponent would hold all planets with their dropdowns. – Zydnar Commented Sep 29, 2019 at 17:45
- lift state up to parent ponent or use something like useContext or redux. – George Commented Sep 29, 2019 at 17:48
5 Answers
Reset to default 5This can be achieved by producing a new set of options for each dropdown on render based off of what are the currently selected options, and what is the selected option for that dropdown.
First make sure each dropdown is binding to a property in your ponent's state and updating on change:
constructor() {
super();
this.state = {
planets: ["a", "b", "c", "d"],
inputs: {
d1: "",
d2: ""
}
};
this.handleChange = this.handleChange.bind(this);
}
handleChange({ target }) {
this.setState({
...this.state,
inputs: {
...this.state.inputs,
[target.name]: target.value
}
});
}
<select
name="d1"
value={this.state.inputs.d1}
onChange={this.handleChange}>
Then you can obtain a list of the selected planets within the render
method by converting the input object into an array using Object.values()
:
const selectedPlanets = Object.values(this.state.inputs);
Then create a new array for each of the dropdowns which will omit any planets which have already been selected unless it is selected by that particular dropdown itself:
const d1Options = this.state.planets.filter(
p => !selectedPlanets.find(sP => sP === p) || p === this.state.inputs.d1
);
const d2Options = this.state.planets.filter(
p => !selectedPlanets.find(sP => sP === p) || p === this.state.inputs.d2
);
<select
name="d1"
value={this.state.inputs.d1}
onChange={this.handleChange}>
<option></option>
{d1Options.map(o => (
<option key={o}>{o}</option>
))}
</select>
I've put together a working example here:
https://codepen.io/curtis__/pen/pozmOmx
You can solve this is multiple ways. Here is pseudo-code. Create an object or array to hold the values of selected index. I will suggest to use useState API. Instead of setState.
class extends Component {
static state = {
dropdown1: "",
dropdown2: "",
dropdown3: "",
dropdown4: ""
}
constructor(props) {
super(props)
}
handleClick = (id, {target: {value}}) => {
this.setState({
[id]: value
})
}
render() {
<div>
<select onChange={this.handleClick.bind(null, "dropdown1")}>
</select>
<select onChange={this.handleClick.bind(null, "dropdown2")}>
</select>
<select onChange={this.handleClick.bind(null, "dropdown3")}>
</select>
<select onChange={this.handleClick.bind(null, "dropdown4")}>
</select>
</div>
}
}
You should replace this line
let select_Planets = this.state.planets.map(planet => {
return planet.name;
});
with
let select_Planets = this.state.planets.filter(planet => !this.state.selected_planets.includes(planet))
This will make sure that the only available options are those that have not been selected already. you can do it for all the other dropdowns.
you also replace the following lines
handleSelect = event => {
this.setState({ selected_planets: [event.target.value] });
};
with
handleSelect = event => {
this.setState({ selected_planets: [...this.state.selected_planets, event.target.value] });
};
You can manage all value in a Mother ponent, passing the selected option to all child ponents.
function DropdownBoxes({ url }) {
const [options, setOptions] = useState([]);
const [selected, setSelected] = useState({ a: 'ABC', b: 'CDE' });
useEffect(() => { // ponentDidMount
fetch(url).then(setOptions);
}, [url]);
const onChange = (key, value) => {
setSelected(prev => ({ // this.setState
...prev,
[key]: value,
}));
}
const filterOptions = Object.values(selected); // ['ABC', 'CDE']
return (
<div>
<DropdownBox
options={options}
filterOptions={filterOptions}
value={selected['a']}
onChange={val => onChange('a', val)}
/>
<DropdownBox
options={options}
filterOptions={selected}
value={selected['b']}
onChange={val => onChange('b', val)}
/>
</div>
)
}
When you render the options, add a filter to show the option only if it is equal to the value, or it is not a subset of filterOptions. If you cannot/ do not want to chnage any code of dropdown box, you can add the filter to mother ponent when to passing options
.
function DropdownBox({options, filterOptions, value, onChange}) {
...
const filter = (opt) => {
return opt.value === value || !filterOptions.includes(opt.value);
}
return (
<select>
{options.filter(filter).map(opt => (
<option value={opt.value} ...>{opt.label}</option>
))}
</select>
)
}
You can create a SelectDropDown
view ponent and render it in the parent ponent. And the selected value for all dropdown is maintained by its parent ponent eg: selectedPlanets
state.
// structure for selectedPlanets state.
type SelectedPlanets = {
[dropDownId: string]: string
};
Rusable SelectDropDown.js
import * as React from 'react';
type Props = {
valueField: string,
primaryKeyField: string, // field like id or value.
selectedValues: Array<string>, // values of the primaryField attribute that uniquely define the objects like id, or value
options: Array<Object>,
handleSelect: (event: SystheticEvent<>) => void
}
export default class SelectDropDown extends React.Component<Props> {
render() {
const {
options,
selectedValues,
primaryKeyField,
valueField,
handleSelect
} = this.props;
const optionsDom = options.map(option => {
if(!selectedValues.includes(option[primaryKeyField])){
return (
<option key={option[primaryKeyField]} value={option[valueField]}>
{planet}
</option>
);
}
});
return (
<select onChange={handleSelect}>
<option defaultChecked></option>
{optionsDom}
</select>
);
}
}
Sudo code Dashboard.js
import * as React from 'react';
import SelectDropDown from "./SelectDropDown"
export default class Dashboard extends React.Component {
state = {
planets: [],
selectedPlanets: {}
};
/*
Assumimg planets struct
[{
planetId: 1,
planet: "Earth"
}]
*/
ponentDidMount() {
fetch('url')
.then(response => {
return response.json();
})
.then(data => {
this.setState({ planets: data });
});
}
handleSelect = (dropDownId, event) => {
const { selectedPlanets } = this.state;
const selectedPlanetsCopy = {...selectedPlanets};
selectedPlanetsCopy[dropDownId] = event.target.value;
this.setState({ selectedPlanets: selectedPlanetsCopy });
};
getSelectedValues = (dropDownId) => {
const {selectedPlanets} = this.state;
const selectedValues = [];
Object.keys(selectedPlanets).forEach((selectedPlanetDropDownId) => {
if(dropDownId !== selectedPlanetDropDownId) {
selectedValues.push(selectedPlanets[selectedPlanetDropDownId]);
}
});
return selectedValues;
}
render() {
const { planets } = this.state;
return (
<SelectDropDown
valueField={"planet"}
primaryKeyField={"planetId"}
selectedValues={this.getSelectedValues("dropDown1")}
options={planets}
handleSelect={this.handleSelect.bind(this, "dropDown1")}
/>
<SelectDropDown
valueField={"planet"}
primaryKeyField={"planetId"}
selectedValues={this.getSelectedValues("dropDown2")}
options={planets}
handleSelect={this.handleSelect.bind(this, "dropDown2")}
/>
);
}
}