Creating dynamic drop downs that depend on another drop-down's value (React/Redux)

Aditya Ps
3 min readJan 12, 2017

--

Today I had this requirement at work where I had to display a set of values in a multiple value drop down depending on what the user selects in another multiple value drop down.

The first drop down allows the user to select a degree which would initiate a network request to get the specializations for that degree and display them as the values of another drop down for the user to select. Each time the user adds a new degree , a network request would be made to fetch the specializations for the newly selected degree and the fetched specializations would be appended to the already stored (in Redux Store) specializations and displayed to the user. This worked fine until the user removes a selected degree. The specializations for the removed degree would still remain in the second drop down for the user to select.

I did not want to make a new network request for each of the remaining degrees as I had their specializations already in the Redux Store.

To solve this, I had to map the degree to their specializations in the store and then go through the mapping and find the entry for the removed degree and slice that entry out of the store. This would then re-render the page and remove the specializations for the removed degree from the second drop down. (Though the mapping created meant I had to remove that mapping in the view to get only the specializations and then flatten the array of all specializations and then create an object for each item and send them to the second drop down)

CODE:

View:

class FilterContent extends React.Component{constructor(props){
super(props);
this.state={
specialization: [],
degrees: []
}

this.handleSpecializationChange = this.handleSpecializationChange.bind(this);

this.handleDegreesChange = this.handleDegreesChange.bind(this);
}
componentDidMount(){

this.props.getUGDegrees();
this.props.getPGDegrees();
}
handleSpecializationChange(e, {value}){this.setState({...this.state, specialization:value})}

handleDegreesChange(e, {value}){
//check if a value was added or removed
if(value.length < this.state.degrees.length){
//if a value was removed
//find which value was removed
let removedDegree = this.state.degrees.filter(findNotInArray(value));
//call an action creator to remove that degree's specializations from Redux Store
this.props.removeSpecialization(removedDegree);
}
else{
//if a value was added
//make a request to get specializations for the new degree selectedStudents
this.props.getDegreeSpecialization(value[value.length-1])
}
this.setState({...this.state, degrees:value})
}
render() {
let degrees = [];
let specializations = [];
if(this.props.UGDegrees.length !== 0){
degrees = this.props.UGDegrees;
if(this.props.PGDegrees.length !== 0){
degrees = [...degrees, ...this.props.PGDegrees];
}
degrees = degrees.map((degree,index) => {
return {key:index, text:degree, value:degree}
})
}
if(this.props.specializations.length !== 0){
specializations = this.props.specializations.map((specialization, index) => {
return specialization.specializations;
})
specializations = [].concat(...specializations);
specializations = specializations.map((spec,index) => {
return {key:index, text:spec, value:spec}
})
}
return (
<div styleName="filterContent">

<Form.Select label="DEGREES" name="degrees" options={degrees} disabled={this.state.plan} placeholder="Search..." value={this.state.degrees} multiple onChange={this.handleDegreesChange} />
<Form.Select label='SPECIALIZATION' name='specialization' options={specializations} search disabled={this.state.plan} placeholder='Search...' value={this.state.specialization} multiple onChange={this.handleSpecializationChange}/><Button positive type="submit">FILTER</Button>
<a className="ui negative button" onClick={this.resetForm}>RESET</a>
</Form>
</div>
);
}
}
function mapStateToProps(state){
return {
UGDegrees: state.createJob.UGDegrees,
PGDegrees: state.createJob.PGDegrees,
specializations: state.createJob.specializations
}
}
const StyledComponent = CSSModules(FilterContent, styles);
export default connect(mapStateToProps, actions)(StyledComponent);

Action Creators:

export function getUGDegrees(){
const request = axios.get(`${ROOT_URL}/GetAllUnderGraduateDegree`);
return {
type: GET_UG_DEGREES,
payload: request
}
}
export function getPGDegrees(){
const request = axios.get(`${ROOT_URL}/GetAllPostGraduateDegree`);
return {
type: GET_PG_DEGREES,
payload: request
}
}
export function getDegreeSpecialization(degree){
return function (dispatch){
axios.get(`${ROOT_URL}/GetDegreeSpecialization?degreeName=${degree}`).
then(response => {
dispatch({
type: GET_DEGREE_SPECIALIZATION,
payload: {degree: degree, specializations: response.data.ResponseObject.result}
})
})
}
}
export function removeSpecialization(degree){
return {
type: REMOVE_SPECIALIZATION,
payload: degree
}
}

Reducers:

export default function(state=INITIAL_STATE, action){
switch(action.type){

case GET_UG_DEGREES: {
return {...state, UGDegrees:action.payload.data.ResponseObject.result}
}
case GET_PG_DEGREES: {
return {...state, PGDegrees:action.payload.data.ResponseObject.result}
}

case GET_DEGREE_SPECIALIZATION: {
return {...state, specializations: [...state.specializations, action.payload]}
}
case REMOVE_SPECIALIZATION:{
const degree = action.payload[0];
let index = 0;
for(let i in state.specializations){
if(state.specializations[i].degree === degree){
index = i;
break;
}
}
return {
...state,
specializations: [
...state.specializations.slice(0,index),
...state.specializations.slice(Number(index)+1, state.specializations.length)
]
}
}
default: return state;
}
}

--

--