我刚开始学习反应并遇到这个问题,我有两个按钮(在菜单项中添加按钮和在购物车中添加按钮)每个定义在不同的组件中。我通过菜单项中添加按钮的道具定义了功能并添加了逻辑,当用户单击时,菜单项会更新数量、cartCount 并将项目详细信息和数量添加到购物车。现在,当我点击购物车中的添加按钮时,我想要一个类似的功能,比如它应该更新购物车中的数量、菜单项中的数量和购物车数量。我知道应该有一些方法可以避免再次重复整个逻辑。我知道这是一个漫长的过程。预先感谢您的回答!!!
App.js
import react, { useState, useEffect } from "react";
import Header from "./components/Header";
import LandingPage from "./components/LandingPage";
import MenuItems from "./components/menuItems";
import Cart from "./components/Cart";
import ItemContext from "./store/item-context";
function App() {
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(() => {
return items.reduce((acc, eachItem) => {
return eachItem.quantity + acc;
}, 0)
})
}, [items])
const [cartBool, setCartBool] = useState(false);
function AddedItem(item) {
const foundIndex = items.findIndex(eachItem => {
return eachItem.title === item.title;
})
if (foundIndex !== -1) {
setItems(prev => {
prev[foundIndex].quantity = item.quantity;
return [...prev];
})
}
else {
setItems(prev => {
return [...prev, item]
})
}
}
function handleCartClick() {
setCartBool(true);
}
function handleCloseClick() {
setCartBool(false);
}
return (
<react.Fragment>
<ItemContext.Provider value={{
items: items
}}>
{cartBool &&
<Cart onCloseClick={handleCloseClick} />}
<div className="parent-container">
<Header cartCount={total} onCartClick={handleCartClick} />
<LandingPage />
<MenuItems onAddItem={AddedItem} />
</div>
</ItemContext.Provider>
</react.Fragment>
);
}
export default App;
Menu-items.js
import react from "react";
import MenuItem from "./menuItem";
import MenuContent from "./menuContent";
function MenuItems(props) {
function handleItems(item){
props.onAddItem(item);
}
return (
<div className="menu">
{MenuContent.map(eachItem =>{
return <MenuItem title={eachItem.title} description={eachItem.description} price={eachItem.price} key={eachItem.key} onAdd={handleItems}/>
})}
</div>
);
}
export default MenuItems;
Menu-item.js
import react , { useState } from "react";
function MenuItem(props) {
const [item, setItem] = useState({
title: "",
quantity: 0,
price: ""
});
function handleClick(){
setItem(prev =>{
return {
title: props.title,
quantity: prev.quantity + 1,
price: props.price
}
})
}
function handleSubmit(event){
event.preventDefault();
props.onAdd(item);
}
return (
<div className="menu-item">
<div className="menu-content">
<h3>{props.title}</h3>
<p>{props.description}</p>
<h4>{props.price}</h4>
</div>
<form onSubmit={handleSubmit} className="add-items">
<label htmlFor="Amount">Amount</label>
<input onChange={() => {}} type="number" name="Amount" value={item.quantity}/>
<button onClick={handleClick} type="submit" className="btn btn-lg">Add</button>
</form>
</div>
);
}
export default MenuItem;`
Cart.js
import react, { useContext } from "react";
import CartItem from "./cartItem";
import ItemContext from "../store/item-context";
function Cart(props) {
const ctx = useContext(ItemContext);
function handleCloseClick(){
props.onCloseClick();
}
return (
<div className="cart-modal">
<div className="card">
{ctx.items.map((eachItem, index) =>{
return <CartItem title={eachItem.title} price={eachItem.price} quantity={eachItem.quantity} key={index} onAdd={props.onAddItem} onRemove={props.RemoveItem}/>
})}
<footer>
<button className="btn btn-lg" onClick={handleCloseClick}>Close</button>
<button className="btn btn-lg">Order</button>
</footer>
</div>
</div>
);
}export default Cart;
cartItem.js
import react, { useState } from "react";
function CartItem(props) {
const [item, setItem] = useState({
title: props.title,
price: props.price,
quantity: props.quantity
})
function handlePlusClick(){
setItem(prev =>{
prev.quantity = prev.quantity + 1
return prev
})
props.onAdd(item);
}
function handleMinusClick(){
var updatedQuantity;
setItem(prev =>{
prev.quantity = prev.quantity -1
updatedQuantity = prev.quantity
return prev;
})
if(updatedQuantity > 0){
props.onAdd(item);
}
else{
props.onRemove(item);
}
}
return (
<div className="cart-item">
<div className="cart-content">
<h1>{props.title}</h1>
<p>{props.price}
<span> X {props.quantity}</span>
</p>
</div>
<div className="button-controls">
<button onClick={handleMinusClick}>-</button>
<button onClick={handlePlusClick}>+</button>
</div>
</div>
);
}export default CartItem;
当用户单击 cartItem 中的 + 按钮并将其发送到 App.js 中的 AddedItem 函数时,我尝试创建新的项目对象并且它正在工作(即使这不是最佳实践)!!!但它也在更新 menuItem 组件中的 item.quantity。它按预期工作......但我不知道为什么它会返回并更新 menuItem 数量。是因为 useContext 并且我将它包裹在我正在渲染的所有组件周围吗??
您的示例仍然有点难以理解和重现,因为我们看不到
MenuContent
并且useContext
的使用令人困惑。
但听起来您的菜单和购物车都在使用相同的
items
状态,或者至少发生了类似的事情。
您的代码展示了状态管理的句柄,但我认为您需要退后一步,思考您的应用程序的哪些部分应该是有状态的以及需要哪些策略。你不需要
useContext
,但我想这是一个说明差异和优势的机会。
现在我假设你的菜单项是一个没有真正改变的项目列表。您的购物车将需要一些状态,因为您需要跟踪商品及其数量,并使用此信息来计算购物车总数。
我们需要在哪里更新或访问我们的购物车状态?
MenuItem
- 我们的菜单项有一个 Add
按钮,该按钮应使用新数量更新购物车状态。我们在这里不需要购物车items,但我们确实需要处理逻辑以更新我们的购物车。
Cart
- 我们的购物车需要访问购物车状态以 a) 显示购物车项目和 b) 增加或减少特定项目(+ 和 -)的数量。
您可以使用到目前为止在您的代码中使用的相同策略(您已共享)通过道具钻孔来做到这一点OR您可以使用
useContext
.
为了证明差异,下面是一个更完整的解决方案
useContext
。购物车的所有状态管理逻辑都捆绑到我们的购物车上下文中,我们的提供商允许我们的应用程序的一部分访问这个without非常依赖道具。
useContext
的示例/演示完整解决方案(点击查看)https://codesandbox.io/s/update-cart-example-use-context-4glul7
import "./styles.css";
import React, { useState, createContext, useContext, useReducer } from "react";
const CartContext = createContext();
const initialCartState = { cartItems: [], totalCost: 0, totalQuantity: 0 };
const actions = {
INCREMENT_ITEM: "INCREMENT_ITEM",
DECREMENT_ITEM: "DECREMENT_ITEM",
UPDATE_QUANTITY: "UPDATE_QUANTITY"
};
const reducer = (state, action) => {
const existingCartItem = state.cartItems.findIndex((item) => {
return item.id === action.itemToUpdate.id;
});
switch (action.type) {
case actions.INCREMENT_ITEM:
return {
cartItems: state.cartItems.map((item) =>
item.id === action.itemToUpdate.id
? {
...item,
quantity: item.quantity + 1
}
: item
),
totalQuantity: state.totalQuantity + 1,
totalCost: state.totalCost + action.itemToUpdate.price
};
case actions.DECREMENT_ITEM:
return {
cartItems: state.cartItems.map((item) =>
item.id === action.itemToUpdate.id
? {
...item,
quantity: item.quantity - 1
}
: item
),
totalQuantity: state.totalQuantity - 1,
totalCost: state.totalCost - action.itemToUpdate.price
};
case actions.UPDATE_QUANTITY:
return {
cartItems:
existingCartItem !== -1
? state.cartItems.map((item) =>
item.id === action.itemToUpdate.id
? {
...item,
quantity: item.quantity + action.itemToUpdate.quantity
}
: item
)
: [...state.cartItems, action.itemToUpdate],
totalQuantity: state.totalQuantity + action.itemToUpdate.quantity,
totalCost:
state.totalCost +
action.itemToUpdate.quantity * action.itemToUpdate.price
};
default:
return state;
}
};
const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialCartState);
const value = {
cartItems: state.cartItems,
totalQuantity: state.totalQuantity,
totalCost: state.totalCost,
incrementItem: (itemToUpdate) => {
dispatch({ type: actions.INCREMENT_ITEM, itemToUpdate });
},
decrementItem: (itemToUpdate) => {
dispatch({ type: actions.DECREMENT_ITEM, itemToUpdate });
},
updateQuantity: (itemToUpdate) => {
dispatch({ type: actions.UPDATE_QUANTITY, itemToUpdate });
}
};
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
};
export default function App() {
return (
<CartProvider>
<MenuItems />
<Cart />
</CartProvider>
);
}
const menuItems = [
{ title: "item 1", description: "description 1", price: 10, id: "1" },
{ title: "item 2", description: "description 2", price: 20, id: "2" },
{ title: "item 3", description: "description 3", price: 30, id: "3" }
];
function MenuItems(props) {
return (
<div className="menu">
{menuItems.map((item) => {
return (
<MenuItem
title={item.title}
description={item.description}
price={item.price}
key={item.id}
// added this as prop
id={item.id}
/>
);
})}
</div>
);
}
function MenuItem(props) {
const { updateQuantity } = useContext(CartContext);
const [item, setItem] = useState({
title: props.title,
quantity: 0,
price: props.price,
// included a unique item id here
id: props.id
});
// Don't need this anymore...
// function handleClick(e) {
// ...
// }
// update quantity as we type by getting as state...
function changeQuantity(e) {
e.preventDefault();
setItem((prev) => {
return {
...prev,
quantity: Number(e.target.value)
};
});
}
function handleSubmit(e, item) {
e.preventDefault();
updateQuantity(item);
}
return (
<div className="menu-item">
<div className="menu-content">
<h3>{props.title}</h3>
<p>{props.description}</p>
<h4>Price: ${props.price}</h4>
</div>
<form onSubmit={(e) => handleSubmit(e, item)} className="add-items">
<label htmlFor="Amount">Amount</label>
<input
onChange={changeQuantity}
type="number"
name="Amount"
value={item.quantity}
/>
{/* No need for onClick on button, onSubmit already handles it */}
<button type="submit" className="btn btn-lg">
Add
</button>
</form>
</div>
);
}
function Cart() {
const {
cartItems,
totalQuantity,
totalCost,
incrementItem,
decrementItem
} = useContext(CartContext);
return (
<div>
<h2>Cart</h2>
<h3>Items:</h3>
{cartItems.length > 0 &&
cartItems.map(
(item) =>
item.quantity > 0 && (
<div key={item.id}>
{item.title}
<br />
<button onClick={() => decrementItem(item)}> - </button>{" "}
{item.quantity}{" "}
<button onClick={() => incrementItem(item)}> + </button>
</div>
)
)}
<h3>Total Items: {totalQuantity}</h3>
<h3>Total Cost: {`$${Number(totalCost).toFixed(2)}`}</h3>
</div>
);
}
听起来您希望在
Add
中单击MenuItem
时更新购物车。
onClick
和onSubmit
这是您问题的一部分。在
MenuItem
中,您使用了一个表单,并且在您的表单提交按钮上有 onClick
。由于您的按钮具有 type="submit"
,它将与 onSubmit
处理程序一起触发提交事件。我们可以在这里简单地使用onSubmit
作为我们的处理程序,并从按钮中删除onClick
。
我简化了
MenuItem
以更新和读取状态中的数量值。然后在添加项目时,我们只需传递该项目(因为它已经具有最新数量)。
你的逻辑基本上就在那里。我给每个产品一个
id
来简化跟踪所有的螺旋桨钻孔,而不是使用 title
或 key
因为它让我更容易理解。希望更改和评论有意义。
https://codesandbox.io/s/update-cart-example-veic1h
import "./styles.css";
import React, { useState, createContext, useContext, useEffect } from "react";
const CartContext = createContext();
export default function App() {
const [cartItems, setCartItems] = useState([]);
const [totalQuantity, setTotalQuantity] = useState(0);
const [totalCost, setTotalCost] = useState(0);
useEffect(() => {
setTotalQuantity(() => {
return cartItems.reduce((acc, item) => {
return item.quantity + acc;
}, 0);
});
setTotalCost(() => {
return cartItems.reduce((acc, item) => {
return item.quantity * item.price + acc;
}, 0);
});
}, [cartItems]);
function addItemToCart(newItem) {
const existingCartItem = cartItems.findIndex((item) => {
return item.id === newItem.id;
});
setCartItems((prevItems) => {
return existingCartItem !== -1
? prevItems.map((prevItem) =>
prevItem.id === newItem.id
? {
...prevItem,
quantity: prevItem.quantity + newItem.quantity
}
: prevItem
)
: [...prevItems, newItem];
});
// the above is similar to what you have below,
// but good practice not to mutate state directly
// in case of incrementing item already found in cart...
// if (foundIndex !== -1) {
// setCartItems((prev) => {
// prev[foundIndex].quantity = item.quantity;
// return [...prev];
// });
// } else {
// setCartItems((prev) => {
// return [...prev, item];
// });
// }
}
return (
<CartContext.Provider value={{ cartItems, totalQuantity, totalCost }}>
<div className="parent-container">
<MenuItems onAddItem={addItemToCart} />
<Cart />
</div>
</CartContext.Provider>
);
}
const menuItems = [
{ title: "item 1", description: "description 1", price: 10, id: "1" },
{ title: "item 2", description: "description 2", price: 20, id: "2" },
{ title: "item 3", description: "description 3", price: 30, id: "3" }
];
function MenuItems(props) {
function handleItems(item) {
props.onAddItem(item);
}
return (
<div className="menu">
{menuItems.map((item) => {
return (
<MenuItem
title={item.title}
description={item.description}
price={item.price}
key={item.id}
// added this as prop
id={item.id}
onAdd={handleItems}
/>
);
})}
</div>
);
}
function MenuItem(props) {
const [item, setItem] = useState({
title: props.title,
quantity: 0,
price: props.price,
// included a unique item id here
id: props.id
});
// Don't need this anymore...
// function handleClick(e) {
// ...
// }
// update quantity as we type by getting as state...
function changeQuantity(e) {
e.preventDefault();
setItem((prev) => {
return {
...prev,
quantity: Number(e.target.value)
};
});
}
function handleSubmit(event) {
event.preventDefault();
props.onAdd(item);
}
return (
<div className="menu-item">
<div className="menu-content">
<h3>{props.title}</h3>
<p>{props.description}</p>
<h4>Price: ${props.price}</h4>
</div>
<form onSubmit={handleSubmit} className="add-items">
<label htmlFor="Amount">Amount</label>
<input
onChange={changeQuantity}
type="number"
name="Amount"
value={item.quantity}
/>
{/* No need for onClick on button, onSubmit already handles it */}
<button type="submit" className="btn btn-lg">
Add
</button>
</form>
</div>
);
}
function Cart() {
const cart = useContext(CartContext);
const { cartItems, totalQuantity, totalCost } = cart;
return (
<div>
<h2>Cart</h2>
<h3>Items:</h3>
{cartItems.length > 0 &&
cartItems.map(
(item) =>
item.quantity > 0 && (
<div key={item.id}>
{item.title} - quantity: {item.quantity}
</div>
)
)}
<h3>Total Items: {totalQuantity}</h3>
<h3>Total Cost: {`$${Number(totalCost).toFixed(2)}`}</h3>
</div>
);
}