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

React+TypeScript多选组件按选中数量禁用按钮的实现问题

问题:React多选组件的条件禁用逻辑实现

我正在开发一个多选组件,该组件接收选项数量、答案数量、回调函数作为props,用于获取用户选中的选项数据,点击按钮时会显示勾选标记。

组件需满足以下逻辑:

  • 当答案数量为1(单选):用户点击任一按钮后,其余按钮需禁用;若再次点击该选中按钮,其余按钮恢复可用。
  • 当答案数量大于1(多选):用户选中的按钮数量达到指定答案数后,其余按钮禁用;若取消选中任一已选按钮,其余按钮恢复可用。

附上现有组件代码:

父组件代码

import MultipleChoice from 'components/MultipleChoice';

const ParentComponent = () => {
  const [curSelectedOptions, setCurSelectedOptions] = useState<number[]>([]);
  
  return (
    <MultipleChoice
      numOfOptions={5}
      numOfAnswers={1}
      onClick={(selectedOption) => {
        setCurSelectedOptions((prev) =>
          prev.includes(selectedOption)
            ? prev.filter((option) => option !== selectedOption)
            : [...prev, selectedOption]
        );
      }}
    />
  );
};

子组件(MultipleChoice)代码

import { useState } from 'react';
import styles from './MultipleChoice.module.scss';
import _ from 'lodash';
import { CheckOutlined } from '@ant-design/icons';

interface IMultipleChoice {
  numOfOptions: number;
  numOfAnswers: number;
  onClick: (selectedOption: number) => void;
}

const MultipleChoice: React.FC<IMultipleChoice> = ({
  numOfOptions,
  numOfAnswers,
  onClick,
}) => {
  const [checkedOption, setCheckedOption] = useState<any>({});
  
  const onHandleClick = (index: any) => {
    setCheckedOption((prev: any) => ({ ...prev, [index]: !prev[index] }));
  }
  
  return (
    <div className={styles.container}>
      {numOfOptions && _.range(1, numOfOptions + 1).map((option, index) => {
        return (
          <button
            key={option}
            value={option}
            onClick={(e) => {
              onHandleClick(e, index); // 参数传递错误
              onClick(e.currentTarget.value);
            }}
            // disabled={} 待实现禁用逻辑
            className={`${styles.choiceButton} ${styles.center}`}
          >
            <div
              className={`${styles.circleOfNumber} ${styles.center}`}
            >
              <span className={styles.numberText}>{option}</span>
              {checkedOption[index] ? ( // 控制勾选标记显示
                <div className={styles.checkMarkWrapper}>
                  <CheckOutlined
                    style={{
                      fontSize: 68,
                      color: 'red',
                    }}
                  />
                </div>
              ) : null}
            </div>
          </button>
        );
      })}
    </div>
  );
};

现有状态说明

checkedOption状态表现:点击5个选项中的1、2按钮,状态为{0: true, 1: true};再次点击相同按钮时,状态变为{0: false, 1: false}而非{}。勾选标记在checkedOption对应index值为true时显示。

目前无法结合现有勾选标记逻辑实现按钮的条件禁用,该如何解决?


解决方案

1. 修正点击事件参数错误

onHandleClick函数需要接收index,原代码传递了事件对象e,需修正为:

onClick={(e) => {
  onHandleClick(index);
  onClick(Number(e.currentTarget.value)); // 把字符串value转为数字更严谨
}}

2. 计算已选中选项数量

新增变量统计当前已选中的选项个数:

const selectedCount = Object.values(checkedOption).filter(isChecked => isChecked).length;

3. 实现条件禁用逻辑

针对disabled属性分场景处理:

  • 单选场景(numOfAnswers === 1):已选中1个选项且当前按钮未被选中 → 禁用
  • 多选场景(numOfAnswers > 1):已选数量达标且当前按钮未被选中 → 禁用

最终disabled条件:

disabled={
  (numOfAnswers === 1 && selectedCount === 1 && !checkedOption[index]) ||
  (numOfAnswers > 1 && selectedCount === numOfAnswers && !checkedOption[index])
}

4. 完整修改后的MultipleChoice组件

import { useState } from 'react';
import styles from './MultipleChoice.module.scss';
import _ from 'lodash';
import { CheckOutlined } from '@ant-design/icons';

interface IMultipleChoice {
  numOfOptions: number;
  numOfAnswers: number;
  onClick: (selectedOption: number) => void;
}

const MultipleChoice: React.FC<IMultipleChoice> = ({
  numOfOptions,
  numOfAnswers,
  onClick,
}) => {
  const [checkedOption, setCheckedOption] = useState<Record<number, boolean>>({});
  
  const onHandleClick = (index: number) => {
    setCheckedOption((prev) => ({ ...prev, [index]: !prev[index] }));
  };

  // 统计已选中的选项数量
  const selectedCount = Object.values(checkedOption).filter(isChecked => isChecked).length;

  return (
    <div className={styles.container}>
      {numOfOptions && _.range(1, numOfOptions + 1).map((option, index) => {
        const isChecked = checkedOption[index] || false;
        return (
          <button
            key={option}
            value={option}
            onClick={(e) => {
              onHandleClick(index);
              onClick(Number(e.currentTarget.value));
            }}
            disabled={
              (numOfAnswers === 1 && selectedCount === 1 && !isChecked) ||
              (numOfAnswers > 1 && selectedCount === numOfAnswers && !isChecked)
            }
            className={`${styles.choiceButton} ${styles.center}`}
          >
            <div className={`${styles.circleOfNumber} ${styles.center}`}>
              <span className={styles.numberText}>{option}</span>
              {isChecked ? (
                <div className={styles.checkMarkWrapper}>
                  <CheckOutlined
                    style={{
                      fontSize: 68,
                      color: 'red',
                    }}
                  />
                </div>
              ) : null}
            </div>
          </button>
        );
      })}
    </div>
  );
};

export default MultipleChoice;

逻辑验证

  • 单选场景:点击任意按钮后其余按钮禁用,再次点击该按钮取消选中,其余按钮恢复可用。
  • 多选场景:选中数量达标后未选中按钮禁用,取消任意已选按钮后,未选中按钮恢复可用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 16:16:05