fefe
fefe

Reputation: 9055

React Select chained options based on another dropdown selected value

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

Answers (1)

aturan23
aturan23

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

Related Questions