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

React中useEffect因删除数量为0商品触发无限循环的解决请求

问题描述

点击+按钮向selectedFoods状态列表添加商品对象;点击-按钮减少商品数量。尝试删除数量为0的商品时,通过过滤selectedFoods列表触发了useEffect无限循环。

问题代码片段:

if (newFood.amount < 1) {
    setSelectedFoods((prevFoods) => {
      return prevFoods.filter((food) => food.id !== newFood.amount.id);
    });
}

完整代码:

export const FoodContextProvider = (props) => {
  const [selectedFoods, setSelectedFoods] = useState([]);
  const [newFood, setNewFood] = useState({});
  const [mode, setMode] = useState(null);
  const [length, setLength] = useState(selectedFoods.length);

  useEffect(() => {
    if (mode === "add") {
      const duplicate = selectedFoods.some((item) => item.id === newFood.id);
      if (duplicate) {
        selectedFoods.forEach((item) => {
          if (item.id === newFood.id) {
            item.amount = newFood.amount;
          }
        });
      } else {
        setSelectedFoods((prevFoods) => {
          return [...prevFoods, newFood];
        });
      }
    } else {
      selectedFoods.forEach((item) => {
        if (item.id === newFood.id) {
          item.amount = newFood.amount;
        }
      });
      if (newFood.amount < 1) {
        setSelectedFoods((prevFoods) => {
          return prevFoods.filter((food) => food.id !== newFood.amount.id);
        });
      }
    }
    console.log(selectedFoods);
  }, [newFood, mode, selectedFoods, setSelectedFoods]);

  const selectingFood = (food) => {
    setLength((pre) => pre + 1);
    setNewFood(food.newFoodItem);
    setMode(food.mode);
  };

  return (
    <FoodContext.Provider
      value={{
        foodItems: foodItem,
        selectingFood: selectingFood,
        selectedFoods: selectedFoods,
        selectedItemLength: length,
      }}
    >
      {props.children} {/* App componnet */}
    </FoodContext.Provider>
  );
};
解决方法

问题根源

  1. 直接修改state破坏不可变性:在useEffect中通过selectedFoods.forEach直接修改数组元素的amount属性,属于直接变更React state(mutate),会导致state引用未变但内容改变,触发useEffect重复执行。
  2. 删除逻辑的语法错误:newFood.amount.id是错误写法——amount是数字类型,不存在id属性,这会导致过滤条件始终为food.id !== undefined,每次都会重新过滤selectedFoods,引发无限循环。
  3. 冗余状态与依赖不当:length状态完全可以通过selectedFoods.length推导,无需单独维护;useEffect依赖了selectedFoods,结合上述state变更问题,进一步加剧了循环。

修复步骤

  1. 严格遵循state不可变性:所有状态更新都使用setSelectedFoods的函数式更新,避免直接修改原state数组。
  2. 修正删除逻辑错误:将newFood.amount.id改为newFood.id,正确匹配要删除的商品ID。
  3. 移除冗余状态:删除length状态,直接在Context中返回selectedFoods.length。
  4. 优化useEffect依赖:移除selectedFoods作为依赖,因为函数式更新可以获取最新的prevState,无需依赖当前state。

修正后的完整代码

export const FoodContextProvider = (props) => {
  const [selectedFoods, setSelectedFoods] = useState([]);
  const [newFood, setNewFood] = useState({});
  const [mode, setMode] = useState(null);

  useEffect(() => {
    if (mode === "add") {
      setSelectedFoods((prevFoods) => {
        // 检查是否已存在该商品
        const exists = prevFoods.some(item => item.id === newFood.id);
        if (exists) {
          // 不可变更新:创建新数组,修改对应商品的amount
          return prevFoods.map(item => 
            item.id === newFood.id ? {...item, amount: newFood.amount} : item
          );
        } else {
          // 添加新商品
          return [...prevFoods, newFood];
        }
      });
    } else if (mode === "subtract") { // 假设mode为subtract时执行减少逻辑
      setSelectedFoods((prevFoods) => {
        // 先更新商品数量
        const updated = prevFoods.map(item => 
          item.id === newFood.id ? {...item, amount: newFood.amount} : item
        );
        // 如果数量小于1,过滤掉该商品
        return updated.filter(item => !(item.id === newFood.id && item.amount < 1));
      });
    }
  }, [newFood, mode]);

  const selectingFood = (food) => {
    setNewFood(food.newFoodItem);
    setMode(food.mode);
  };

  return (
    <FoodContext.Provider
      value={{
        foodItems: foodItem,
        selectingFood: selectingFood,
        selectedFoods: selectedFoods,
        selectedItemLength: selectedFoods.length, // 直接计算长度
      }}
    >
      {props.children}
    </FoodContext.Provider>
  );
};

额外说明

  • 函数式更新setSelectedFoods(prev => ...)确保每次更新都基于最新的state快照,避免闭包陷阱。
  • 移除了直接修改state的操作,所有更新都返回新的数组/对象,符合React的不可变性原则。
  • 明确了mode的分支判断(假设减少操作对应mode="subtract"),让逻辑更清晰。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 20:34:52