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> ); };
解决方法
问题根源
- 直接修改state破坏不可变性:在
useEffect中通过selectedFoods.forEach直接修改数组元素的amount属性,属于直接变更React state(mutate),会导致state引用未变但内容改变,触发useEffect重复执行。 - 删除逻辑的语法错误:
newFood.amount.id是错误写法——amount是数字类型,不存在id属性,这会导致过滤条件始终为food.id !== undefined,每次都会重新过滤selectedFoods,引发无限循环。 - 冗余状态与依赖不当:
length状态完全可以通过selectedFoods.length推导,无需单独维护;useEffect依赖了selectedFoods,结合上述state变更问题,进一步加剧了循环。
修复步骤
- 严格遵循state不可变性:所有状态更新都使用
setSelectedFoods的函数式更新,避免直接修改原state数组。 - 修正删除逻辑错误:将
newFood.amount.id改为newFood.id,正确匹配要删除的商品ID。 - 移除冗余状态:删除
length状态,直接在Context中返回selectedFoods.length。 - 优化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
相关产品推荐
相关产品推荐

