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

React中基于复选框开关增删state项及解决key警告方法

问题根因
  • 现有逻辑在同一个useEffect内连续触发多次setProducts,每次更新都基于上一次的临时状态做增删,当两个复选框状态同时变化时,会重复添加商品,导致作为key的商品title重复,触发React key警告。
  • products是复选框状态的派生值,完全不需要单独维护状态、也不需要用useEffect监听修改,直接根据当前勾选状态计算即可,从根源避免状态不同步问题。
修复后完整代码
const root = ReactDOM.createRoot(document.getElementById("root"))

const initialArray = [
  {
    title: "Belts",
    for: "men"
  },
  {
    title: "Makeup",
    for: "women"
  }
];

function App() {
  const [menToggle, setMenToggle] = React.useState(false);
  const [womenToggle, setWomenToggle] = React.useState(false);

  // 直接根据勾选状态筛选要展示的商品,无需额外维护products状态
  const products = initialArray.filter(item => {
    if (item.for === "men") return menToggle;
    if (item.for === "women") return womenToggle;
    return false;
  });

  return (
    <div className="App">
      <label>
        <input
          type="checkbox"
          checked={menToggle}
          onChange={() => setMenToggle((prevValue) => !prevValue)}
        />
        男士
      </label>
      <label>
        <input
          type="checkbox"
          checked={womenToggle}
          onChange={() => setWomenToggle((prevValue) => !prevValue)}
        />
        女士
      </label>
      <ul>
        {products.map((item) => <li key={item.title}>{item.title}</li>)}
      </ul>
    </div>
  );
}

root.render(<App />);

对应的HTML入口代码:

<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>

<div id="root"></div>
核心修改点
  • 移除冗余的products状态和对应的useEffect更新逻辑,彻底解决多次调用setState导致的重复数据问题
  • 直接通过数组filter方法从源商品数据中筛选符合当前勾选规则的商品,逻辑简洁,不会出现状态不一致
  • 将商品列表外层容器从div替换为语义化的ul标签,符合HTML规范(li标签需要作为ul/ol的子元素)
  • 删除多余的非空判断:空数组调用map不会抛出错误,会直接返回空内容,不需要额外判断products存在且长度大于0

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 10:30:39