Antd动态下拉框联动异常:新增行后二级下拉值同步问题
Antd 动态行下拉框联动异常解决

我使用Antd实现了每行包含两个Select组件的表单,点击+图标可新增一行相同的双下拉框。
需求
- 每行第一个下拉框选中值后,第二个下拉框展示对应关联数据
- 新增行后,该行第一个下拉框选择值时,对应的第二个下拉框也需展示关联数据
问题
所有行的第二个下拉框数据始终同步变化。选择第二行第一个下拉框的值时,第二行第二个下拉框显示对应数据,但第一行的第二个下拉框也会跟着变成相同数据。单行时联动正常,新增行后出现该问题,求可行的实现方案。
以下是我的代码片段(可能无法在部分环境运行):
import React, { useRef, useEffect, useState } from "react"; import { Form, Select, Col, Row, Button, Checkbox } from "antd/lib"; import { PlusOutlined, MinusOutlined } from "@ant-design/icons"; const { Option } = Select; const DropdownComponent = (props) => { const addBtnRef = useRef(null); const [dropDownOneData, setDropDownOneData] = useState([]); const [dropDownTwoData, setDropDownTwoData] = useState([]); const [dropDownOneValue, setDropDownOneValue] = useState(""); const dropDownOneTwoValues: [ [ { dropDownOne: { key: "test_t"; value: "test"; }; dropDownTwo: [ { key: "key1"; value: "Value1"; }, { key: "key2"; value: "Value2"; } ]; } ], [ { dropDownOne: { key: "test_t1"; value: "test1"; }; dropDownTwo: [ { key: "key3"; value: "Value3"; }, { key: "key4"; value: "Value4"; } ]; } ] ]; useEffect(() => { addBtnRef.current.click(); // set dropdownone values const dropDownOneData = dropDownOneTwoValues?.map( (searchField) => searchField[0].dropDownOne ); setDropDownOneData(dropDownOneData); }, []); useEffect(() => { // set related dropDownTwo values on selected dropdownOne value const dropDownTwoValues = dropDownOneTwoValues .flat() .filter((values) => values.dropDownOne.key === dropDownOneValue) .flatMap((value) => value.dropDownTwo); setDropDownTwoData(dropDownTwoValues); }, [dropDownOneValue]); const handleDropDownOne = (value) => { setDropDownOneValue(value); }; function renderDropDownValues(options) { return options.map((option) => { if (option.key && option.value) { return ( <Option key={option.key} value={option.key} disabled={option.disabled} > {option.value} </Option> ); } return ( <Option key={option} value={option}> {option} </Option> ); }); } return ( <> <Row> <Col> <Form.List> {(fields, { add, remove }) => { { console.log("fields...", fields); } return ( <div className="border" style={{ padding: "20px", marginBottom: "25px", marginTop: "-30px" }} > {fields.map((value, index) => { return ( <Row key={index} style={{ marginBottom: 8 }}> <Col> <Form.Item rules={[ { required: true, message: "Select a value" } ]} name={value.Name} > <Select key={index} className="w-100" showSearch={true} optionFilterProp="children" filterOption={(input, option) => option?.children .toLowerCase() .includes(input.toLowerCase()) } placeholder="Select" onChange={(value) => handleDropDownOne(value)} value={dropDownOneValue} > {renderDropDownValues(dropDownOneData)} </Select> </Form.Item> </Col> <Col> <Form.Item rules={[ { required: true, message: "Select a value" } ]} name={value.name} > <Select className="w-100" showSearch={true} optionFilterProp="children" filterOption={(input, option) => option?.children .toLowerCase() .includes(input.toLowerCase()) } placeholder="Select" > {renderDropDownValues(dropDownTwoData)} </Select> </Form.Item> </Col> </Row> ); })} <Row justify="end"> <Col> <Form.Item> <Button ref={addBtnRef} shape="circle" size="small" className="center" data-testid="add-clause-btn" icon={ <PlusOutlined className="primary-color" style={{ marginTop: "1px" }} /> } onClick={() => { add(); }} /> </Form.Item> </Col> </Row> </div> ); }} </Form.List> ) </Col> </Row> </> ); }; export default DropdownComponent;
问题根源
你的代码中dropDownOneValue和dropDownTwoData是全局状态,所有行共享这两个状态。当任意一行的第一个下拉框触发onChange时,会修改全局的dropDownOneValue,进而更新全局的dropDownTwoData,导致所有行的第二个下拉框都同步显示相同数据。
解决思路
给每一行维护独立的联动状态,不需要全局存储选中值和二级下拉选项,而是通过Form.List的字段值,实时根据当前行第一个下拉框的选中值过滤出对应的二级选项。
修改后的代码
import React, { useRef, useEffect, useState } from "react"; import { Form, Select, Col, Row, Button } from "antd/lib"; import { PlusOutlined } from "@ant-design/icons"; const { Option } = Select; const DropdownComponent = (props) => { const addBtnRef = useRef(null); const [dropDownOneData, setDropDownOneData] = useState([]); // 移除全局的dropDownOneValue和dropDownTwoData // 整理数据源为更易访问的结构,key为一级下拉的key,value为对应的二级选项 const dropDownMap = {}; const dropDownOneTwoValues = [ [ { dropDownOne: { key: "test_t", value: "test" }, dropDownTwo: [ { key: "key1", value: "Value1" }, { key: "key2", value: "Value2" } ] } ], [ { dropDownOne: { key: "test_t1", value: "test1" }, dropDownTwo: [ { key: "key3", value: "Value3" }, { key: "key4", value: "Value4" } ] } ] ]; useEffect(() => { addBtnRef.current.click(); // 初始化一级下拉数据,同时构建映射表 const firstLevelData = dropDownOneTwoValues?.map(item => item[0].dropDownOne); setDropDownOneData(firstLevelData); // 构建一级key到二级选项的映射 dropDownOneTwoValues.flat().forEach(item => { dropDownMap[item.dropDownOne.key] = item.dropDownTwo; }); }, []); function renderDropDownValues(options) { return options.map((option) => { if (option.key && option.value) { return ( <Option key={option.key} value={option.key} disabled={option.disabled} > {option.value} </Option> ); } return <Option key={option} value={option}>{option}</Option>; }); } return ( <> <Row> <Col> <Form.List name="dynamicRows"> {(fields, { add, remove }) => { return ( <div className="border" style={{ padding: "20px", marginBottom: "25px", marginTop: "-30px" }} > {fields.map((field, index) => { return ( <Row key={field.key} style={{ marginBottom: 8, gap: 16 }}> <Col flex={1}> <Form.Item rules={[{ required: true, message: "请选择值" }]} name={[field.name, "dropDownOne"]} > <Select className="w-100" showSearch={true} optionFilterProp="children" filterOption={(input, option) => option?.children.toLowerCase().includes(input.toLowerCase()) } placeholder="请选择" > {renderDropDownValues(dropDownOneData)} </Select> </Form.Item> </Col> <Col flex={1}> {/* 使用Form.Item的getFieldValue获取当前行一级下拉的选中值 */} <Form.Item rules={[{ required: true, message: "请选择值" }]} name={[field.name, "dropDownTwo"]} dependencies={[[field.name, "dropDownOne"]]} > {({ getFieldValue }) => { const selectedFirst = getFieldValue([field.name, "dropDownOne"]); const secondOptions = selectedFirst ? dropDownMap[selectedFirst] : []; return ( <Select className="w-100" showSearch={true} optionFilterProp="children" filterOption={(input, option) => option?.children.toLowerCase().includes(input.toLowerCase()) } placeholder="请选择" > {renderDropDownValues(secondOptions)} </Select> ); }} </Form.Item> </Col> <Col> {fields.length > 1 && ( <Button shape="circle" size="small" icon={<PlusOutlined rotate={180} />} onClick={() => remove(field.name)} /> )} </Col> </Row> ); })} <Row justify="end"> <Col> <Form.Item> <Button ref={addBtnRef} shape="circle" size="small" icon={<PlusOutlined className="primary-color" />} onClick={() => add()} /> </Form.Item> </Col> </Row> </div> ); }} </Form.List> </Col> </Row> </> ); }; export default DropdownComponent;
关键改动说明
- 移除全局状态:删除了
dropDownOneValue和dropDownTwoData,避免全局状态共享导致的同步问题。 - 构建数据源映射:在初始化时将一级下拉key和对应的二级选项存入
dropDownMap,方便快速查找。 - Form.List字段结构优化:每行的两个下拉框使用
[field.name, "dropDownOne"]和[field.name, "dropDownTwo"]作为name,确保每行数据独立存储。 - 实时获取当前行选中值:在二级下拉框的Form.Item中,通过
getFieldValue获取当前行一级下拉的选中值,实时过滤出对应的二级选项,同时设置dependencies确保一级下拉值变化时重新渲染二级下拉框。 - 新增删除按钮:添加了删除行的功能,让动态行更完整。
内容的提问来源于stack exchange,提问作者Soumya Behera
相关产品推荐
相关产品推荐

