如何使按钮上的 onClick 事件与 react 中为其他按钮定义的 onClick 事件的工作方式相同

问题描述 投票:0回答:1

我刚开始学习反应并遇到这个问题,我有两个按钮(在菜单项中添加按钮和在购物车中添加按钮)每个定义在不同的组件中。我通过菜单项中添加按钮的道具定义了功能并添加了逻辑,当用户单击时,菜单项会更新数量、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 并且我将它包裹在我正在渲染的所有组件周围吗??

javascript reactjs react-hooks react-props react-dom
1个回答
0
投票

响应 OP 2/18 的更新

您的示例仍然有点难以理解和重现,因为我们看不到

MenuContent
并且
useContext
的使用令人困惑。

但听起来您的菜单和购物车都在使用相同的

items
状态,或者至少发生了类似的事情。

您的代码展示了状态管理的句柄,但我认为您需要退后一步,思考您的应用程序的哪些部分应该是有状态的以及需要哪些策略。你不需要

useContext
,但我想这是一个说明差异和优势的机会。

状态管理概述

现在我假设你的菜单项是一个没有真正改变的项目列表。您的购物车将需要一些状态,因为您需要跟踪商品及其数量,并使用此信息来计算购物车总数。

我们需要在哪里更新或访问我们的购物车状态?

  1. MenuItem
    - 我们的菜单项有一个
    Add
    按钮,该按钮应使用新数量更新购物车状态。我们在这里不需要购物车items,但我们确实需要处理逻辑以更新我们的购物车

  2. 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>
  );
}
© www.soinside.com 2019 - 2024. All rights reserved.