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

React中useState与多复选框不同步问题求助

问题描述

我正在构建一个多复选框组件,用useState把选中的值存入数组,但出现状态不同步问题:

  • 选中红色选项,数组无新增值
  • 选中蓝色选项,数组仅加入之前选中的红色值
  • 选中绿色选项,数组仅包含红色和蓝色值

组件代码

import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { filterProducts } from "../../redux/Shopping/shopping-actions";

const filterValues = [
  {
    heading: "Colour",
    items: ["Red", "Blue", "Green"],
  },
  {
    heading: "Gender",
    items: ["Men", "Women"],
  },
  {
    heading: "Price",
    items: ["0-Rs250", "Rs251-450", "Rs 450"],
  },
  {
    heading: "Type",
    items: ["Polo", "Hoodie", "Classic"],
  },
];

const Filter = ({ filter, searchArray, setSearchArray }) => {
  
  const handleCheck = (e) => {
    if (e.target.checked === true) {
      setSearchArray([...searchArray, e.target.value]);
    } else {
      setSearchArray(searchArray.filter((id) => id !== e.target.value));
    }
    if (searchArray.length > 0) {
      filter(searchArray);
    }
  };
  return (
    <div>
      <aside className="ml-4 mt-16">
        {filterValues.map((item) => {
          return (
            <>
              <h3 className="mb-4 mt-2 ml-4 font-semibold text-gray-900 dark:text-black">
                {item.heading}
              </h3>

              <ul className="w-48 text-sm font-medium text-black-900 bg-white dark:text-white">
                {item.items.map((i) => {
                  return (
                    <li className="w-full rounded-t-lg">
                      <div className="flex items-center pl-3">
                        <input
                          id="checkbox"
                          type="checkbox"
                          value={i}
                          onChange={(e) => handleCheck(e)}
                          className="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"
                        />
                        <label
                          for="checkbox"
                          className="py-3 ml-2 w-full text-sm font-medium text-gray-900 dark:text-black"
                        >
                          {i}
                        </label>
                      </div>
                    </li>
                  );
                })}
              </ul>
            </>
          );
        })}
      </aside>
    </div>
  );
};

const mapDispatchToProps = (dispatch) => {
  return {
    filter: (query) => dispatch(filterProducts(query)),
  };
};

export default connect(null, mapDispatchToProps)(Filter);

Redux筛选Reducer代码

case actionTypes.FILTER_PRODUCT:
      const searchParameters = data && Object.keys(Object.assign({}, ...data));
      let filterData;
      console.log("testing", action.payload);

      if (action.payload.length === 0) {
        filterData = state.products;
      } else {
        action.payload.forEach((i) => {
          filterData = state.products.filter((item) =>
            searchParameters.some((parameter) =>
              item[parameter]
                .toString()
                .toLowerCase()
                .includes(i.toLowerCase())
            )
          );
        });
      }
      return {
        ...state,
        filteredData: filterData,
      };
    default:
      return state;
  }
问题根源
  1. useState异步更新特性:setSearchArray是异步操作,调用后searchArray不会立刻刷新,所以紧接着调用filter(searchArray)时,用的还是更新前的旧数组。
  2. 复选框ID重复:所有复选框的id都是checkbox,点击标签时可能触发错误的复选框,属于潜在bug。
  3. Reducer筛选逻辑错误:forEach循环每次都会覆盖filterData,最终只保留最后一个筛选条件的结果,无法实现多条件叠加筛选。
解决方案

1. 修复useState异步问题

用函数式更新获取最新状态,或者通过useEffect监听状态变化触发筛选:

方案一:函数式更新内触发筛选

const handleCheck = (e) => {
  const value = e.target.value;
  setSearchArray(prev => {
    const newArray = e.target.checked 
      ? [...prev, value] 
      : prev.filter(id => id !== value);
    // 用最新的数组调用筛选
    filter(newArray);
    return newArray;
  });
};

方案二:useEffect监听状态变化

useEffect(() => {
  filter(searchArray);
}, [searchArray, filter]);

const handleCheck = (e) => {
  const value = e.target.value;
  if (e.target.checked) {
    setSearchArray(prev => [...prev, value]);
  } else {
    setSearchArray(prev => prev.filter(id => id !== value));
  }
};

2. 修复复选框ID重复问题

给每个复选框生成唯一ID,同时把JSX中的for属性改为htmlFor:

{item.items.map((i) => {
  const uniqueId = `checkbox-${item.heading}-${i}`;
  return (
    <li className="w-full rounded-t-lg" key={uniqueId}>
      <div className="flex items-center pl-3">
        <input
          id={uniqueId}
          type="checkbox"
          value={i}
          onChange={(e) => handleCheck(e)}
          // 其他属性不变
        />
        <label
          htmlFor={uniqueId}
          // 其他属性不变
        >
          {i}
        </label>
      </div>
    </li>
  );
})}

3. 修复Reducer多条件筛选逻辑

用reduce替代forEach,实现多条件叠加筛选:

case actionTypes.FILTER_PRODUCT:
  const searchParameters = data && Object.keys(Object.assign({}, ...data));
  let filterData;

  if (action.payload.length === 0) {
    filterData = state.products;
  } else {
    // 逐步叠加所有筛选条件
    filterData = action.payload.reduce((acc, filterValue) => {
      return acc.filter(item => 
        searchParameters.some(parameter => 
          item[parameter]
            .toString()
            .toLowerCase()
            .includes(filterValue.toLowerCase())
        )
      );
    }, state.products);
  }

  return {
    ...state,
    filteredData: filterData,
  };
完整修改后的组件代码
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { filterProducts } from "../../redux/Shopping/shopping-actions";

const filterValues = [
  {
    heading: "Colour",
    items: ["Red", "Blue", "Green"],
  },
  {
    heading: "Gender",
    items: ["Men", "Women"],
  },
  {
    heading: "Price",
    items: ["0-Rs250", "Rs251-450", "Rs 450"],
  },
  {
    heading: "Type",
    items: ["Polo", "Hoodie", "Classic"],
  },
];

const Filter = ({ filter, searchArray, setSearchArray }) => {
  
  useEffect(() => {
    filter(searchArray);
  }, [searchArray, filter]);

  const handleCheck = (e) => {
    const value = e.target.value;
    if (e.target.checked) {
      setSearchArray(prev => [...prev, value]);
    } else {
      setSearchArray(prev => prev.filter(id => id !== value));
    }
  };

  return (
    <div>
      <aside className="ml-4 mt-16">
        {filterValues.map((item) => {
          return (
            <>
              <h3 className="mb-4 mt-2 ml-4 font-semibold text-gray-900 dark:text-black">
                {item.heading}
              </h3>

              <ul className="w-48 text-sm font-medium text-black-900 bg-white dark:text-white">
                {item.items.map((i) => {
                  const uniqueId = `checkbox-${item.heading}-${i}`;
                  return (
                    <li className="w-full rounded-t-lg" key={uniqueId}>
                      <div className="flex items-center pl-3">
                        <input
                          id={uniqueId}
                          type="checkbox"
                          value={i}
                          onChange={(e) => handleCheck(e)}
                          className="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-700 focus:ring-2 dark:bg-gray-600 dark:border-gray-500"
                        />
                        <label
                          htmlFor={uniqueId}
                          className="py-3 ml-2 w-full text-sm font-medium text-gray-900 dark:text-black"
                        >
                          {i}
                        </label>
                      </div>
                    </li>
                  );
                })}
              </ul>
            </>
          );
        })}
      </aside>
    </div>
  );
};

const mapDispatchToProps = (dispatch) => {
  return {
    filter: (query) => dispatch(filterProducts(query)),
  };
};

export default connect(null, mapDispatchToProps)(Filter);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 20:45:17