You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

Antd 动态行下拉框联动异常解决

DropDown Image

我使用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;

关键改动说明

  1. 移除全局状态:删除了dropDownOneValue和dropDownTwoData,避免全局状态共享导致的同步问题。
  2. 构建数据源映射:在初始化时将一级下拉key和对应的二级选项存入dropDownMap,方便快速查找。
  3. Form.List字段结构优化:每行的两个下拉框使用[field.name, "dropDownOne"]和[field.name, "dropDownTwo"]作为name,确保每行数据独立存储。
  4. 实时获取当前行选中值:在二级下拉框的Form.Item中,通过getFieldValue获取当前行一级下拉的选中值,实时过滤出对应的二级选项,同时设置dependencies确保一级下拉值变化时重新渲染二级下拉框。
  5. 新增删除按钮:添加了删除行的功能,让动态行更完整。

内容的提问来源于stack exchange,提问作者Soumya Behera

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 23:50:27