코드스테이츠 9주차 / Cmarket (Hooks)

✏️Achievement Goals

✅ useState 를 이용해 상태를 사용하는 방법을 학습합니다.
✅ [장바구니 담기] 버튼을 이용해 장바구니에 해당 상품이 추가되도록 구현하세요.
✅ 장바구니 내 [삭제] 버튼을 이용해 장바구니의 상품이 제거되도록 구현하세요.
✅ 장바구니의 상품 갯수의 변동이 생길 때마다, 상단 내비게이션 바에 상품 갯수가 업데이트되도록 구현하세요.

📝summary

이미 어느정도 완성되어있는 쇼핑몰 홈페이지가 있었고 여기서
장바구니 담기, 삭제, 상품갯수 업데이트기능을 작동하도록 구현하면 됐다

제일 상위폴더인 app.js에 이미 작성되어있는 아이템들의 정보와 카트에 담겨있는 아이템의 정보를 각 컴포넌트에 내려줘서 문제를 해결 할 수 있다

const [items, setItems] = useState(initialState.items);
const [cartItems, setCartItems] = useState(initialState.cartItems);

이미 각 컴포넌트에 handleClick들이 이름만 작성이 되어 있어서
그곳을 채워넣는 형태로 구현해 줬다

여기서 중요한것은 setItems setCartItems 변경하는 함수들도 props로 넘겨줄 수 있다는 것!

app.js

import React, { useState } from 'react';
import Nav from './components/Nav';
import ItemListContainer from './pages/ItemListContainer';
import './App.css';
import {
  BrowserRouter as Router,
  Switch,
  Route,
} from "react-router-dom";
import ShoppingCart from './pages/ShoppingCart';
import { initialState } from './assets/state';

function App() {

  const [items, setItems] = useState(initialState.items);
  const [cartItems, setCartItems] = useState(initialState.cartItems);


  return (
    <Router>
      <Nav cartItems={cartItems}/>
      <Switch>
        <Route exact={true} path="/">
          // cartItems={cartItems} setCartItems={setCartItems}을 props로 넘겨준다
          <ItemListContainer items={items} cartItems={cartItems} setCartItems={setCartItems }/>
        </Route>
        <Route path="/shoppingcart">
          <ShoppingCart cartItems={cartItems} items={items} setCartItems={setCartItems}/>
        </Route>
      </Switch>
    </Router>
  );
}

export default App;

1. 장바구니 추가

ItemListContainer의 handleClick을 item 컴포넌트에서 받아서 사용해 장바구니에 추가해주는 형태였다
이때 item의 장바구니 담기 onClick에 onClick={(e) => handleClick(e, item.id)} 이미 인자가 두개가 작성이 되어 있는걸 보고 시작해야 한다

장바구니에 담기를 구현할때는 고려해줘야 할 사항이 몇가지 있었는데
장바구니에 없는 아이템은 추가해주고
있는 아이템이면 수량만 추가해줘야 했다

ItemListContainer.js

import React from 'react';
import Item from '../components/Item';

function ItemListContainer({ items, cartItems, setCartItems }) {
  const handleClick = ( e, id ) => { 
    let newCartItem = {};
    newCartItem.itemId = id;
    newCartItem.quantity = 1;
    for(let i = 0; i < cartItems.length; i++){
      if(cartItems[i].itemId === id){
        setCartItems([...cartItems])
        cartItems[i].quantity++
      }else{
        setCartItems([...cartItems, newCartItem])
      }
    }
  }
  return (
    <div id="item-list-container">
      <div id="item-list-body">
        <div id="item-list-title">쓸모없는 선물 모음</div>
        {items.map((item, idx) => <Item item={item} key={idx} handleClick={handleClick} />)}
      </div>
    </div>
  );
}

export default ItemListContainer;

나는 포문을 돌려서 해결했는데 페어님은 findIndex를 사용해서 구현한 코드가 인상적이었다

  const handleClick = (e, Id) => {
   let original= cartItems
   let findNumber = cartItems.findIndex((e)=>e.itemId===Id)
    if(findNumber!==-1){
     original[findNumber].quantity+=1
     setCartItems(original)
    }else {
      setCartItems ([...original, 
        {"itemId": Id,"quantity": 1}])
    }    

클릭을 하면 클릭한 값의 id를 가져온다.
클릭한 값과 현재 있는 값의 id 를 비교해서 있으면 그냥 추가만 하고
없으면 새로운 값을 추가해준다.

item.js

import React from 'react'

export default function Item({ item, handleClick }) {

  return (
    <div key={item.id} className="item">
      <img className="item-img" src={item.img} alt={item.name}></img>
      <span className="item-name">{item.name}</span>
      <span className="item-price">{item.price}</span>
      <button className="item-button" onClick={(e) => handleClick(e, item.id)}>장바구니 담기</button>
    </div>
  )
}

2. 장바구니 삭제

handleDelete함수를 수정해서 사용하면 된다
setCartItems(cartItems.filter((el)=> el.itemId !== itemId))
cartItems에 필터를 돌려서 cartItems의 el은 객체형태로 되어있으므로 el.itemId와 클릭된 itemId값을 비교해서 같은 값만 삭제되도록 하면 된다

3. 장바구니 수량 변경

handleQuantityChange함수를 수정해서 사용하면 된다

  const handleQuantityChange = (quantity, itemId) => {
    const newCartItems = [...cartItems]
    let findIdx = cartItems.findIndex((item) => item.itemId === itemId)
    newCartItems[findIdx].quantity = quantity
    setCartItems(newCartItems)
  }

findIndex를 사용해서 인덱스 번호를 찾아준 뒤
newCartItems[인덱스번호]의 수량 = 받아온 수량 으로 바꿔주면 된다
이렇게 하면 장바구니에서 위 아래 버튼을 클릭해서도 수량 변경이 가능하다

  "cartItems": [
    {
      "itemId": 1,
      "quantity": 1
    },
    {
      "itemId": 5,
      "quantity": 7
    },
    {
      "itemId": 2,
      "quantity": 3
    }
  ]

ShoppingCart.js

import React, { useState } from 'react'
import CartItem from '../components/CartItem'
import OrderSummary from '../components/OrderSummary'

export default function ShoppingCart({ items, cartItems, setCartItems }) {
  const [checkedItems, setCheckedItems] = useState(cartItems.map((el) => el.itemId))

  const handleQuantityChange = (quantity, itemId) => {
    const newCartItems = [...cartItems]
    let findIdx = cartItems.findIndex((item) => item.itemId === itemId)
    newCartItems[findIdx].quantity = quantity
    setCartItems(newCartItems)
  }
  
  const handleDelete = (itemId) => {
    setCheckedItems(checkedItems.filter((el) => el !== itemId))
    setCartItems(cartItems.filter((el)=> el.itemId !== itemId))
  }

  return (
    <div id="item-list-container">
      <div id="item-list-body">
        <div id="item-list-title">장바구니</div>
        <span id="shopping-cart-select-all">
          <input
            type="checkbox"
            checked={
              checkedItems.length === cartItems.length ? true : false
            }
            onChange={(e) => handleAllCheck(e.target.checked)} >
          </input>
          <label >전체선택</label>
        </span>
        <div id="shopping-cart-container">
          {!cartItems.length ? (
            <div id="item-list-text">
              장바구니에 아이템이 없습니다.
            </div>
          ) : (
              <div id="cart-item-list">
                {renderItems.map((item, idx) => {
                  const quantity = cartItems.filter(el => el.itemId === item.id)[0].quantity
                  return <CartItem
                    key={idx}
                    handleCheckChange={handleCheckChange}
                    handleQuantityChange={handleQuantityChange}
                    handleDelete={handleDelete}
                    item={item}
                    checkedItems={checkedItems}
                    quantity={quantity}
                  />
                })}
              </div>
            )}
          <OrderSummary total={total.price} totalQty={total.quantity} />
        </div>
      </div >
    </div>
  )
}

CartItem.js

import React from 'react'

export default function CartItem({
  item,
  checkedItems,
  handleCheckChange,
  handleQuantityChange,
  handleDelete,
  quantity
}) {
  return (
    <li className="cart-item-body">
      <input
        type="checkbox"
        className="cart-item-checkbox"
        onChange={(e) => {
          handleCheckChange(e.target.checked, item.id)
        }}
        checked={checkedItems.includes(item.id) ? true : false} >
      </input>
      <div className="cart-item-thumbnail">
        <img src={item.img} alt={item.name} />
      </div>
      <div className="cart-item-info">
        <div className="cart-item-title" data-testid={item.name}>{item.name}</div>
        <div className="cart-item-price">{item.price}</div>
      </div>
      <input
        type="number"
        min={1}
        className="cart-item-quantity"
        value={quantity}
        onChange={(e) => {
          handleQuantityChange(Number(e.target.value), item.id)
        }}>
      </input>
      <button className="cart-item-delete" onClick={() => { handleDelete(item.id) }}>삭제</button>
    </li >
  )
}

4. Nav바 장바구니 수량 변경

제일 쉬운 부분이다

app.js에서 nav바에 cartItems을 넘겨준뒤

app.js

<Nav cartItems={cartItems}/>

nav.js

{cartItems.length} 로 수량을 바꿔주면 끝난다

import React from 'react';
import { Link } from 'react-router-dom';

function Nav({cartItems}) {

  return (
    <div id="nav-body">
      <span id="title">
        <img id="logo" src="../logo.png" alt="logo" />
        <span id="name">CMarket</span>
      </span>
      <div id="menu">
        <Link to="/">상품리스트</Link>
        <Link to="/shoppingcart">
          장바구니<span id="nav-item-counter">{cartItems.length}</span>
        </Link>
      </div>
    </div>
  );
}

export default Nav;

시작하기 전에...
애플리케이션에서 사용하는 주요 상태는 다음과 같습니다. 시작하기 전에 반드시 컴포넌트 구조와, 데이터 흐름을 먼저 그림으로 그려보세요. 일을 보다 단순하게 만들 수 있습니다.

다시 한번 컴포넌트 구조를 파악하고 시작하는게 중요하구나 깨달았던 하루
너무 어렵다^~^

좋은 웹페이지 즐겨찾기