본문 바로가기

카테고리 없음

2.React json-Server 활용 프로젝트 애견용품샵 만들기(Haechan_Pet_Shop) 장바구니 Cart 컴포넌트 만들기

 

 

 

 

 

 

  const handleAddToCart = (item) => {
    setCartItem((prev) => {
      const existItem = prev.find((i) => i.id === item.id);
      if (existItem) {
        return prev.map((i) =>
          i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
        );
      } else {
        return [...prev, { ...item, quantity: 1 }];
      }
    });
  };

 

App.js에 설정한 handleAddToCart 함수이다. 자세한 설명은 https://whdrns9771.tistory.com/229에서 확인이 가능하다. 

 

Cart 컴포넌트 전체 코드
import "../../resources/content/cart.css";
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCheck } from "@fortawesome/free-solid-svg-icons";
import { faMinus } from "@fortawesome/free-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import {faCircleXmark} from "@fortawesome/free-solid-svg-icons";

export default function Cart({ items, deleteItem, updateQuantity }) {
  const navigator = useNavigate();

  const gotoHome = () => {
    navigator("/");
  };

  const [checkedItems, setCheckItems] = useState([]);

  const handleDeleted = (items) => {
    deleteItem(items);
    setCheckItems((prev) => prev.filter((id) => id !== items.id));
  };

  /** => 체크박스 변화 핸들러 ::  */
  const handleCheckedItems = (itemId) => {
    setCheckItems((prev) =>
      prev.includes(itemId)
        ? prev.filter((id) => id !== itemId)
        : [...prev, itemId]
    );
  };
  const totalPrice = items
    .filter((item) => checkedItems.includes(item.id))

    /** acc :: accumulator(누산기)
     *  배열의 모든 요소를 돌면서 acc 값에 누적을 한다.
     */
    .reduce((acc, item) => acc + item.price * item.quantity, 0);

  return (
    <div className="cart-area">
      <div className="cart-container">
        <h1>🛒 장바구니</h1>
        {items.length === 0 ? (
          <div className="empty-cart-div">
            <p>장바구니가 비었습니다.</p>
            <button onClick={() => gotoHome()}>홈으로</button>
          </div>
        ) : (
          <ul className="cart-ul">
            {items.map((item, idx) => (
              <li className="cart-li" key={idx}>
                <div className="div-cart-info">
                  <div
                    className="cart-checkbox-div"
                    onClick={() => handleCheckedItems(item.id)}
                  >
                    <input
                      type="checkbox"
                      value={item.id}
                      name="checkedItem"
                      readOnly
                      style={{ display: "none" }}
                      checked={checkedItems.includes(item.id)}
                    />
                    <FontAwesomeIcon
                      icon={faCheck}
                      className={`check-icon ${
                        checkedItems.includes(item.id) ? "checked" : ""
                      }`}
                    />
                  </div>

                  <div className="cart-item-info">
                    <p>{item.name}</p>
                    <p>{(item.price * item.quantity).toLocaleString()}</p>

                    {/**
                     *   onClick={deleteItem(item)}는 즉시 실행됨
                     */}
                  </div>

                  <div className="quantity-update-btn">
                    <p>
                      선택 수량: {item.quantity}
                      <button onClick={() => updateQuantity(item, 1)}>
                        <FontAwesomeIcon
                          icon={faPlus}
                          className="faMinus-icon"
                        />
                      </button>
                      <button onClick={() => updateQuantity(item, -1)}>
                        <FontAwesomeIcon
                          icon={faMinus}
                          className="faMinus-icon"
                        />
                      </button>
                    </p>
                  </div>
                  <div className="cart-info-btn">
                    <button onClick={() => handleDeleted(item)}>
                      <FontAwesomeIcon icon={faCircleXmark}
                        className="cart-xmark-icon"
                      />
                    </button>
                  </div>
                </div>
              </li>
            ))}
          </ul>
        )}

        <div className="totalPriceInpo">
          <div>선택한 {checkedItems.length}개의 상품</div>
          <div>금액 {totalPrice.toLocaleString()}</div>
          <div>
            배송비 {(totalPrice >= 80000 ? 0 : 3000).toLocaleString()}
          </div>
          <div>
            총금액 :{" "}
            {(totalPrice + (totalPrice >= 80000 ? 0 : 3000)).toLocaleString()}
          </div>
        </div>

        <div>결제</div>
      </div>
    </div>
  );
}

 


 

 

  const [checkedItems, setCheckItems] = useState([]);

 

Cart안에 아이템 등을 저장하기 위한 useState 

  /** => 체크박스 변화 핸들러 ::  */
  const handleCheckedItems = (itemId) => {
    setCheckItems((prev) =>
      prev.includes(itemId)
        ? prev.filter((id) => id !== itemId)
        : [...prev, itemId]
    );
  };
 

 

check된 아이템을 찾는다. 

setCheckItems 기존 배열안에 check 모양 버튼을 클릭 시 

기존 checkItems가 있다면[선택 X   체크 해제] 기존 배열과 전달 받은 아이템을 제외하고 새로운 배열을 

만든다. 해당 아이템이 기존에 없다면 기존 체크된 아이템들과 전달 받은 아이템을 setCheckItems에 저장을 한다. 

 

"immutable 방식(불변성 유지 방식)"

 

기존 값을 직접 변경하지 않고, 새로운 값을 만들어서 상태를 업데이트한다. 

React의 상태(state)를 다룰 때 불변성을 유지해야 컴포넌트가 올바르게 렌더링이 된다.

해당 방식은 기존 실무에서 사용이 되는 방식이다. 

 

삭제 함수  App.js
  const [cartItem, setCartItem] = useState([]);
 
  const handleDeleteItem = (deleteItem) => {
    if (window.confirm("정말 삭제하시겠습니까?")) {
      setCartItem((prev) => prev.filter((cartItem) => cartItem !== deleteItem));
    }
  };

 

Cart 컴포넌트
  const handleDeleted = (items) => {
    deleteItem(items);
    setCheckItems((prev) => prev.filter((id) => id !== items.id));
  };

const handleDeleted = (item) => { deleteItem(item);

// 부모에서 받은 삭제 함수 실행

 

setCheckItems((prev) => prev.filter((id) => id !== item.id));

// 체크된 항목에서도 제거 };

 

// 작성하면서 느낀 점 중복되는 함수가 많다. 공통 함수로 바꿔야 겠다. 

 

 

 

items.length === 0 ? (
          <div className="empty-cart-div">
            <p>장바구니가 비었습니다.</p>
            <button onClick={() => gotoHome()}>홈으로</button>
          </div>

 

전달 받은 아이템이 없다면[items.length === 0] 장바구니가 비었다는 설명을 목록에 표시를 한다. 

totalPrice – 선택된 상품들의 총 가격 계산

  const totalPrice = items
    .filter((item) => checkedItems.includes(item.id))

    /** acc :: accumulator(누산기)
     *  배열의 모든 요소를 돌면서 acc 값에 누적을 한다.
     */
    .reduce((acc, item) => acc + item.price * item.quantity, 0);

 

accumulate

 

.reduce() 함수 

모든 요소를 순회하면서 하나의 결과값을 "누적(accumulate)"**하는 데 사용하는  메서드이다.

 

 

    <div className="totalPriceInpo">
          <div>선택한 {checkedItems.length}개의 상품</div>
          <div>금액 {totalPrice.toLocaleString()}</div>
          <div>
            배송비 {(totalPrice >= 80000 ? 0 : 3000).toLocaleString()}
          </div>
          <div>
            총금액 :{" "}
            {(totalPrice + (totalPrice >= 80000 ? 0 : 3000)).toLocaleString()}
          </div>
        </div>

        <div>결제</div>

 

선택한 상품의 갯수는 checkItems = useState 상태로 값을 낸다. 

배송비 : reduce로 계산한 값(totalPrice)가 8만원보다 같거나 많으면 배송비는 0원이되고 

총금액 : 마찬가지로 해당 금액을 같이 계산한다.