快速概览:我有一个包含meals的上下文。该meals用于 * MealDetails-Component *。在此组件中,有一个列表,其中填充了一定数量的 * Food-Components *。如果您单击Food组件中的X,则会将其删除,否则您将转至FoodDetailspage
然后,当我按下按钮时,会执行名为 * deleteFood * 的函数,该函数将meals状态设置为新状态,但不包含该食物。问题是,在此组件中,函数中的setState不是调用一次,而是调用两次。我尝试在其他2个组件中使用该函数,其中一次仅执行一次,另一次执行4次。
- 更新**
我的addFood函数有一个非常相似的问题。但是这个函数在另一个组件中被调用。它在2个不同的上下文中将Food添加到2个不同的状态,并且在这两个上下文中,添加的值都是双倍的。我可以"发现"的一件事是,我浏览器中的控制台第二次打印了我控制台在setState函数中记录的某个值,但不是通过MealsContext。而是通过react_devtools_backend. js。这只发生在我有错误的那两个函数上。
- 更新2我在MealsDetails组件中显示食物组件,该组件包含来自MealsContext的meals**。这是问题所在吗?
MealsDetails.tsx
const MealDetails = () => {
const navigate = useNavigate();
let { id } = useParams();
const { meals } = useMeals();
const style = { "--percentage": "75%" } as React.CSSProperties;
return (
<div className="MealDetails">
<header className="BarcodeScannerHeader">
<ArrowBackIcon
onClick={() => {
navigate("/");
}}
sx={{ fontSize: 35 }}
></ArrowBackIcon>
<div className="HeaderText">{meals.meals[Number(id)].name}</div>
</header>
<div className="MealDetailsContent">
<span className="label">{meals.meals[Number(id)].calories} ate</span>
<hr className="SolidMealDetails"></hr>
{meals.meals[Number(id)] != null
? meals.meals[Number(id)].food.map((food: any, index: number) => <Food key={index} food={food}/>)
: "Not loading"}
</div>
<Link className="Link" to={`/AddFood/${id!}`}>
<div className="MealDetailsAddFoodButton">
<img className="SVG" id="SVG" src={SVG} alt="+" />
</div>
</Link>
</div>
);
};
MealsContext.tsx
let initialState: MealsType = {
calories: 0,
carbs: 0,
meals: [
{
calories: 0,
carbs: 0,
food: []
}
],
};
const MealsProvider = ({ children }: any) => {
const [meals, setMeals] = useState<MealsType>(initialState);
const addFood = async (
id: number,
addedFood: FoodType,
selectedLocation: string,
date: Date
) => {
let res = await fetch("/meals/addFood", {
method: "PATCH",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
mealId: id,
addFood: addedFood,
selectedLocation: selectedLocation,
date: date,
}),
});
const data = await res.json();
if (res.status === 200) {
console.log("func")
setMeals((prevMeals) => {
console.log("state");
const updatedMeals = prevMeals;
let foodExists = false;
updatedMeals.calories += addedFood.kcal;
updatedMeals.carbs += addedFood.carbs;
updatedMeals.meals[id].calories += addedFood.kcal;
updatedMeals.meals[id].carbs += addedFood.carbs;
updatedMeals.meals[id].food.forEach((food : FoodType) => {
if(food.code === addedFood.code){
food.kcal += addedFood.kcal;
food.carbs += addedFood.carbs;
foodExists = true;
}
})
if(!foodExists){
updatedMeals.meals[id].food.push(addedFood);
}
return {...prevMeals,updatedMeals};
});
setUser(prevUser => {
const updatedStorage = prevUser.storage;
updatedStorage.map((storage : any) => {
if(storage.location === selectedLocation){
storage.storedFood.map((storedFood : any) => {
if(storedFood.code === addedFood.code){
storedFood.weight -= addedFood.weight;
}
})
}
})
return {...prevUser, updatedStorage};
})
console.log(data);
} else {
console.log(data);
}
};
const deleteFood = (id: number, deletedFood: FoodType) => {
setMeals((prevMeals) => {
const updatedMeals = prevMeals;
updatedMeals.calories -= deletedFood.kcal;
updatedMeals.carbs -= deletedFood.carbs;
updatedMeals.meals[id].calories -= deletedFood.kcal;
updatedMeals.meals[id].carbs -= deletedFood.carbs;
for(let i = 0; i < updatedMeals.meals[id].food.length; i++){
if(updatedMeals.meals[id].food[i].code === deletedFood.code){
updatedMeals.meals[id].food.splice(i, 1);
}
}
return {...prevMeals,updatedMeals};
});
};
return (
<MealsContext.Provider
value={{
meals,
deleteFood
}}
>
{children}
</MealsContext.Provider>
);
};
const useMeals = () => useContext(MealsContext);
export { MealsProvider, useMeals };
};
Food.tsx
const Food = ({ food }: any) => {
const location = useLocation();
const {deleteFood} = useMeals();
return (
<Link className="FoodContent Link" to={`/FoodDetails/`} state= {{ food: food , location: location.pathname}} >
<div className="Food">
<div className="FoodDetail">
<div className="FoodNameFoodDelete">
<div className="FoodName">{food.name}</div>
<img className="FoodDelete SVG" onClick={(e : any) => {e.preventDefault(); deleteFood(parseInt(location.pathname[location.pathname.length - 1]), food)}} src={SVG}></img>
</div>
<div className="FoodGram-FoodKcal">
<div className="FoodBrand-FoodGram">
<div className="FoodBrand">{food.brand + ","} </div>
<div className="FoodGram">
{food.weight ? food.weight + " g" : 100 + " g"}{" "}
</div>
</div>
<div className="FoodKcal">{food.kcal} kcal</div>
</div>
</div>
<div className="FoodNutritions">
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.carbs*10)/10} g</div>
<div className="FoodNutritionText">Carbs</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.protein*10)/10} g</div>
<div className="FoodNutritionText">Protein</div>
</div>
<div className="FoodNutrition">
<div className="FoodNutritionGrams">{Math.round(food.fat*10)/10} g</div>
<div className="FoodNutritionText">Fat</div>
</div>
</div>
</div>
</Link>
);
};
1条答案
按热度按时间qnakjoqk1#
主要问题是
addFood()
和deleteFood()
函数更改了状态,并且 * 此状态与上下文提供程序位于同一组件中 *,上下文提供程序为所有子组件设置状态。请记住,当您setState时,将导致该状态组件及其子组件重新呈现。更好的解决方案是将状态设置逻辑与上下文提供程序分离。
MealsContext.ts
之前:MealsContext.ts
之后:useMethods.ts
新的异步方法挂接:注意这里的主要变化只是从你的Provider中移除async / state,并返回上下文自身的setState(
setMeals
)方法。现在Provider被设置一次,当状态从async方法设置状态改变时,不会被重新挂载。现在只有钩子和它的消费者会重新呈现。你需要更新现有的使用
addFood()
和deleteFood()
的文件,从新创建的钩子导入这些方法,而不是直接从上下文导入:因此,与此相反
你会用钩子
上下文和状态可能很难一起处理,但是如果您尝试分离关注点并将组件分解为更小的部分,则会更容易理解问题是什么!