Reputation: 9055
Using React Select Async what are the best practices to chain options.
What I mean: I have 3 dropdowns the first one is populated from default with option values and the next 2 dropdowns are disabled.
Selecting the first dropdown value should populate the second dropdown options based on it's value and so on with the next dropdown.
so what I've been trying
import React from "react";
import Select from "react-select";
import AsyncSelect from "react-select/async";
import classnames from "classnames";
import Requests from "../services/requests";
const filterOptions = (inputValue, options) => {
return options.filter(i =>
i.label.toLowerCase().includes(inputValue.toLowerCase())
);
};
class FieldsRenderer extends React.Component {
constructor(props) {
super(props);
this.state = {
fields: props.fields,
containerClass: props.containerClass,
stepSnapshot: null,
selectOptions: {}
};
this.props.fields.map( (f) => {
if(f.type === 'select' && typeof f.dependsOn !== 'undefined') {
this.state.selectOptions[f.name] = null;
}
})
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.fields !== prevState.fields) {
return {
fields: nextProps.fields,
containerClass: nextProps.containerClass
};
}
return null;
}
componentDidUpdate(prevProps, nextProps) {
if (prevProps !== this.props) {
this.setState({
fields: nextProps.fields,
containerClass: nextProps.containerClass
});
this.props.fields.map(f => {
if (typeof f.dependsOn != "undefined") {
this.state.selectOptions[f.name] = null;
}
});
}
}
handleInputChange = (index, e) => {
console.log(e.target.value);
console.log(index);
};
handleSelectChange = (selectedOption, item) => {
this.setState({
stepSnapshot: {
[item.name]: {
value: selectedOption.value,
label: selectedOption.label
}
}
});
let childField = this.props.fields.filter(t => {
if (t.type === "select" && typeof t.dependsOn !== "undefined") {
return t.dependsOn === item.name;
}
});
if (childField) {
this.loadChildOptions(childField[0], selectedOption);
}
};
//load child slect options
loadChildOptions(target, parentValue) {
Requests.get(
process.env.REACT_APP_API_BASE_URL +
target.source +
"/" +
parentValue.value,
(status, data) => {
//data will be set but will be shown just the previous state
this.state.selectOptions[target.name] = data;
}
);
}
render() {
let containerClass = "";
let fields = this.state.fields.map((field, i) => {
const fieldType = field.type;
let fieldStyle;
if (
typeof this.state.containerClass !== "undefined" &&
this.state.containerClass !== ""
) {
containerClass = this.state.containerClass;
}
if (typeof field.width !== "undefined" && field.width !== "") {
fieldStyle = {
width: "calc(" + field.width + " - 5px)"
};
}
switch (fieldType) {
case "select": {
const selectCustomStyles = {
control: (base, state) => ({
...base,
boxShadow: state.isFocused ? 0 : 0,
borderWidth: 2,
height: 45,
borderColor: state.isFocused ? "#707070" : base.borderColor,
"&:hover": {
borderColor: state.isFocused ? "#707070" : base.borderColor
}
}),
option: (provided, state) => ({
...provided,
backgroundColor: state.isSelected ? "#46B428" : "initial"
})
};
if (
typeof field.async !== "undefined" &&
typeof field.dependsOn === "undefined"
) {
return (
<div key={i} className={"field-wrapper"}>
<AsyncSelect
loadOptions={(inputValue, callback) => {
Requests.get(
process.env.REACT_APP_API_BASE_URL + field.source,
(status, data) => {
callback(data);
}
);
}}
styles={selectCustomStyles}
defaultOptions
name={field.name}
placeholder={field.label}
onChange={this.handleSelectChange}
/>
</div>
);
} else if(typeof field.dependsOn !== "undefined") {
return(<div key={i} className={"field-wrapper"}>
<AsyncSelect
styles={selectCustomStyles}
placeholder={field.label}
defaultOptions={this.state.selectOptions[field.name]}
loadOptions={this.state.selectOptions[field.name]}
/>
</div>)
} else {
const disabled =
typeof field.dependsOn !== "undefined" && field.dependsOn !== ""
? this.state.selectOptions[field.name] != null
? false
: true
: false;
return (
<div key={i} className={"field-wrapper"}>
<Select
styles={selectCustomStyles}
placeholder={field.label}
//isLoading={this.state.selectOptions[field.name].length ? true : false}
isDisabled={disabled}
name={field.name}
options={this.state.selectOptions[field.name]}
/>
</div>
);
}
}
case "input":
{
let suffix;
let inputAppendClass;
if (typeof field.suffix !== "undefined" && field.suffix !== "") {
inputAppendClass = "input-has-append";
suffix = <span className={"input-append"}>{field.suffix}</span>;
}
return (
<div
key={i}
className={classnames("field-wrapper input", inputAppendClass)}
style={fieldStyle}
>
<input
placeholder={field.label}
type="text"
className={"input-field"}
onChange={event => this.handleInputChange(field.name, event)}
/>
{suffix}
</div>
);
}
break;
case "checkbox":
{
containerClass = "checkbox-fields";
let radios = field.options.map((option, b) => {
return (
<div key={i + b} className={"field-wrapper checkbox-button"}>
<input
placeholder={option.label}
id={option.name + "_" + i + b}
type={"checkbox"}
className={"input-field"}
/>
<label htmlFor={option.name + "_" + i + b}>
<div className={"label-name"}>{option.label}</div>
<span className={"info-icon"}></span>
<div className={"hint"}>{option.hint}</div>
</label>
</div>
);
});
return radios;
}
break;
case "radio":
{
let radios = field.options.map((option, k) => {
return (
<div key={i + k} className={"field-wrapper radio-button"}>
<input
name={option.name}
id={option.name + "_" + i + k}
placeholder={option.label}
type={"radio"}
className={"input-field"}
/>
<label htmlFor={option.name + "_" + i + k}>
<div className={"label-name"}>{option.label}</div>
<div className={"hint"}>{option.hint}</div>
</label>
</div>
);
});
return radios;
}
break;
default:
break;
}
});
return (
<div className={classnames("fields-group", containerClass)}>{fields}</div>
);
}
}
export default FieldsRenderer;
Upvotes: 6
Views: 5514
Reputation: 5410
For example I has react-select
Async field. I use for manage form formik
. At first you create field:
<AsyncSelect
name="first"
...
onChange={(name, value) => {
// you can write what you want but here small example what I do for other
// two fields
setFieldValue('second', null);
setFieldValue('third', null);
return setFieldValue(name, value);
}}
/>
And second
field:
<AsyncSelect
name="second"
key={!!values.first && !!values.first.id ? values.first.id : null}
...
onChange={(name, value) => {
setFieldValue('third', null);
return setFieldValue(name, value);
}}
/>
There is i give key
and change key
value on changes first field. Because if you don't do it second
field don't know when first
field changes value. And if you give uniq changeable key
second can load from remote data which depends from first
field.
And third
field:
<AsyncSelect
name="third"
key={!!values.third && !!values.third.id ? values.third.id : null}
...
onChange={setFieldValue}
/>
This is easy way to manage depended three or more fields. I think this you understand this logic.
Upvotes: 3