Как заставить событие onClick на кнопке работать так же, как событие onClick, определенное для другой кнопки в реакции

Я только начал учиться реагировать и столкнулся с этой проблемой, когда у меня есть две кнопки (кнопка «Добавить» в элемент меню и кнопка «Добавить в корзину»), каждая из которых определена в разных компонентах. Я определил функциональность и добавил логику с помощью реквизитов для кнопки «Добавить» в элементе меню, который, когда пользователь нажимает, обновляет количество, 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;

Меню-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 и отправил его в функцию AddedItem в App.js, и он работает (хотя это не лучшая практика) !!! Но он также обновляет item.quantity в компоненте menuItem. он работает, как и ожидалось... Но я понятия не имею, почему он возвращается и обновляет количество элементов меню. это из-за useContext, и я обернул его вокруг всех компонентов, которые я визуализирую??

🤔 А знаете ли вы, что...
С JavaScript можно создавать интерактивные формы и проверять введенные пользователем данные.


1
85
1

Ответ:

Решено

Обновления в ответ на OP 2/18

Ваш пример все еще немного сложно воспроизвести и воспроизвести, поскольку мы не видим MenuContent, а использование useContext сбивает с толку.

Но похоже, что и ваше меню, и корзина используют одно и то же состояние items или, по крайней мере, что-то в этом роде происходит.

Ваш код демонстрирует управление состоянием, но я думаю, вам нужно сделать шаг назад и подумать о том, какие части вашего приложения должны иметь состояние и какие стратегии необходимы. Вам не нужно useContext, но я полагаю, что это возможность проиллюстрировать различия и преимущества.

Обзор управления состоянием

Сейчас я предполагаю, что ваши пункты меню представляют собой список пунктов, которые на самом деле не меняются. Для вашей корзины потребуется некоторое состояние, так как вам нужно отслеживать товары вместе с их количеством и использовать эту информацию для расчета общей суммы корзины.

Где нам нужно обновить или получить доступ к нашему состоянию корзины?

  1. MenuItem - В нашем пункте меню есть кнопка Add, которая должна обновить состояние корзины с новым количеством. Здесь нам не нужны элементы корзины, но нам нужно обработать логику обновления нашей корзины.

  2. Cart - Наша корзина должна получить доступ к состоянию корзины, чтобы а) показать элементы корзины и б) увеличить или уменьшить количество определенных элементов (+ и -).

Вы можете сделать это с помощью просверливания, используя те же стратегии, которые использовались в вашем коде до сих пор (которыми вы поделились), ИЛИ вы можете использовать useContext.

Чтобы продемонстрировать разницу, ниже приведено более полное решение с useContext. Вся логика управления состоянием корзины встроена в контекст нашей корзины, и наш провайдер позволяет частям нашего приложения получить к ней доступ, не слишком полагаясь на реквизиты.

Пример/демонстрационное полное решение с 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>
  );
}