繁体   English   中英

在 React 中,如何在多个 select 下拉菜单之间更新 state,每个下拉菜单都有相同的选项?

[英]In React, How to update state between multiple select dropdowns , each with same options?

我有一个问题,我必须渲染 4 个下拉菜单,每个下拉菜单都有类似的选项(我在这里只渲染了 1 个)。 如果我 select 在任何一个下拉列表中的选项,该选项不应该在其他三个中可用。

我应该如何更新 state 中的 selected_planets? 下面的代码从 1 select 下拉列表更新 selected_planets。 但是仍然可以在任何地方使用相同的选项,并且我无法在 selected_planets 数组中获得 4 个不同的选项? 我应该如何进行?

此外,来自 API fetch 的响应是一个对象数组,我通过它映射并在行星数组中更新。 出于演示目的,让我们考虑行星:[海王星、土星、火星、地球、金星、木星]

import React, { Component } from 'react';

export default class Dashboard extends Component {
  state = {
    planets: [],
    selected_planets: []
  };

  componentDidMount() {
    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>
    );
  }
}

这可以通过根据当前选择的选项以及该下拉列表的选择选项为渲染时的每个下拉菜单生成一组新选项来实现。

首先确保每个下拉列表都绑定到组件的 state 中的属性并在更改时更新:

  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}>

然后,您可以通过使用Object.values()将输入 object 转换为数组来获取render方法中选定行星的列表:

const selectedPlanets = Object.values(this.state.inputs);

然后为每个下拉列表创建一个新数组,该数组将省略任何已选择的行星,除非它是由该特定下拉列表本身选择的:

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>

我在这里整理了一个工作示例:

https://codepen.io/curtis__/pen/pozmOmx

您可以通过多种方式解决此问题。 这是伪代码。 创建一个 object 或数组来保存所选索引的值。 我会建议使用 useState API。 而不是 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>
  }

}

你应该替换这一行

  let select_Planets = this.state.planets.map(planet => {
    return planet.name;
   });

 let select_Planets = this.state.planets.filter(planet => !this.state.selected_planets.includes(planet))

这将确保唯一可用的选项是那些尚未选择的选项。 您可以对所有其他下拉菜单执行此操作。

您还替换以下行

handleSelect = event => {
   this.setState({ selected_planets: [event.target.value] });
 };

 handleSelect = event => {
    this.setState({ selected_planets: [...this.state.selected_planets, event.target.value] });
  };

您可以管理母组件中的所有值,将选定的选项传递给所有子组件。

function DropdownBoxes({ url }) {
    const [options, setOptions] = useState([]);
    const [selected, setSelected] = useState({ a: 'ABC', b: 'CDE' });

    useEffect(() => { // componentDidMount
        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>
    )
}

渲染选项时,添加一个过滤器以仅在选项等于该值时才显示该选项,或者它不是 filterOptions 的子集。 如果您不能/不想更改下拉框的任何代码,您可以在传递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>
    )
}

您可以创建一个SelectDropDown视图组件并将其呈现在父组件中。 并且所有下拉列表的选定值由其父组件维护,例如: selectedPlanets state。

// selectedPlanets state 的结构。

type SelectedPlanets = {
  [dropDownId: string]: string
}; 

可使用的 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>
    );
  }
}

须藤代码 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" 
   }]
  */
  componentDidMount() {
    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")}
      />
    );
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM