React

Cmarket (Hooks 버전)/ useEffect

mellomello.made 2022. 7. 6. 10:30

Cmarket (Hooks 버전)

Hooks를 이용해 상태를 다루는 것이 목적입니다. 또한 컴포넌트끼리 상태를 주고받는 과정을 연습할 수 있습니다.

 

TODO: 장바구니에 추가 및 상품 개수 업데이트

다음과 같이 작동해야 합니다.

  • 메인 화면에서 [장바구니 담기] 버튼을 누른 후, 장바구니 페이지로 이동하면 상품이 담겨있어야 합니다.
  • 내비게이션 바에 상품 개수가 즉시 표시되어야 합니다.
App.js

import React, { useState } from "react";
import Nav from "./components/Nav";
import ItemListContainer from "./pages/ItemListContainer";
import "./App.css";
import { BrowserRouter as Router, Routes, 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 itemsLength={cartItems.length} />
      <Routes>
        <Route
          path="/"
          element={
            <ItemListContainer items={items} setCartItems={setCartItems} />
          }
        />
        <Route
          path="/shoppingcart"
          element={
            <ShoppingCart
              cartItems={cartItems}
              items={items}
              setCartItems={setCartItems}
            />
          }
        />
      </Routes>
    </Router>
  );
}

export default App;

 

itemListContainer.js

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

function ItemListContainer({ items, setCartItems }) {
  const handleClick = (e, id) => {
    const newCartItem = {
      itemId: id,
      quantity: 1,
    };
    //prev에 가져온 데이터 추가하기 
    setCartItems((prev) => [...prev, 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;

TODO: 장바구니로부터 제거

  • 장바구니 페이지에서 [삭제] 버튼을 누른 후, 해당 상품이 목록에서 삭제되어야 합니다.
  • 내비게이션 바에 상품 개수가 즉시 표시되어야 합니다.
Shoppingcart.js

import React, { useEffect, 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 [total, setTotal] = useState({});

  // useEffect 실행조건 넣을 수 있는 곳은 []
  // useEffect(()=>{ 실행할코드 }, [count])
  //cartItems  변수가 변할 때만 useEffect 안의 코드가 실행됨.
  useEffect(() => {
    setTotal(getTotal());
  }, [cartItems]);

  const handleCheckChange = (checked, id) => {
    if (checked) {
      setCheckedItems([...checkedItems, id]);
    } else {
      setCheckedItems(checkedItems.filter((el) => el !== id));
    }
  };

  const handleAllCheck = (checked) => {
    if (checked) {
      setCheckedItems(cartItems.map((el) => el.itemId));
    } else {
      setCheckedItems([]);
    }
  };

  const handleQuantityChange = (quantity, itemId) => {
    // quantity: e.target.value(아이템 개수), itemId : item.id
    // cartItems => item.id 의 quantity 변경해주기
    let targetItemidx;
    cartItems.forEach((el, idx) =>
      el.itemId === itemId ? (targetItemidx = idx) : null
    );

    setCartItems(
      cartItems.map((el, idx) =>
        idx === targetItemidx ? { itemId: el.itemId, quantity: quantity } : el
      )
    );

    //cartItems[targetItemidx].quantity = quantity;
  };

  const handleDelete = (itemId) => {
    setCheckedItems(checkedItems.filter((el) => el !== itemId));
    setCartItems(cartItems.filter((el) => el.itemId !== itemId));
  };

  const getTotal = () => {
    let cartIdArr = cartItems.map((el) => el.itemId);
    let total = {
      price: 0,
      quantity: 0,
    };
    for (let i = 0; i < cartIdArr.length; i++) {
      if (checkedItems.indexOf(cartIdArr[i]) > -1) {
        //checkedItems에 cartIdArr 요소가 있는지 판별
        let quantity = cartItems[i].quantity;
        let price = items.filter((el) => el.id === cartItems[i].itemId)[0]
          .price;

        total.quantity = total.quantity + quantity;
        total.price = total.price + quantity * price;
      }
    }
    return total;
  };

  const renderItems = items.filter(
    (el) => cartItems.map((el) => el.itemId).indexOf(el.id) > -1
  );
  //const total = getTotal();

  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  lue={quantity}
        onChange={(e) => {
          handleQuantityChange(Number(e.target.value), item.id)
        }}>
      </input>
      <button className="cart-item-delete" onClick={() => { handleDelete(item.id) }}>삭제</button>
    </li >
  )
}

 

React에서 Lifecycle hook 쓰는 법

useEffect import해오고 콜백함수 추가해서 안에 코드 적으면 이제 그 코드는 컴포넌트가 mount & update시 실행된다.

그래서 Lifecycle hook 라고 한다. 

컴포넌트의 핵심 기능은 html 렌더링이다. 조금이라도 html 렌더링이 빠른 사이트를 원하면 오래걸리는 반복연산, 서버에서 데이터가져

오는 작업, 타이머 다는것 등 기능들은 useEffect 안에 넣는다. useEffect 안에 적은 코드는 html 렌더링 이후에 동작하기 때문이다.

 

useEffect에 넣을 수 있는 실행조건 
useEffect(
()=>{ 실행할코드 }, [count])

 

useEffect()의 둘째 파라미터로 [ ] 를 넣을 수 있는데 변수나 state같은 것들을 넣을 수 있다.

그렇게 하면 [ ]에 있는 변수나 state 가 변할 때만 useEffect 안의 코드를 실행한다.

그래서 위의 코드는 count라는 변수가 변할 때만 useEffect 안의 코드가 실행된다. 

(참고) [ ] 안에 state 여러개 넣을 수 있다.

 

useEffect(
()=>
{ 실행할코드 }, [])

아무것도 안넣으면 컴포넌트 mount시 (로드시) 1회 실행하고 영영 실행해주지 않는다.

 

useEffect(()=>{ 
  2. 그 다음 실행됨 
  return ()=>{
    1. 여기있는게 먼저실행됨
  }
}, [count])

useEffect 동작하기 전에 특정코드를 실행하고 싶으면 return ()=>{} 안에 넣을 수 있다. 

 useEffect 안에 있는 코드를 실행하기 전에 return ()=>{ } 안에 있는 코드를 실행해준다.   이것을 clean up function 이라고 칭한다. 

 

useEffect(()=>{ 
  let a = setTimeout(()=>{ setAlert(false) }, 2000)
  return ()=>{
    clearTimeout(a)
  }
}, [])

 

setTimeout() 쓸 때마다 브라우저 안에 타이머가 하나 생긴다. useEffect 안에 썼기 때문에 컴포넌트가 mount 될 때 마다 실행되고

잘못 코드를 짜면 타이머가 100개 1000개 생길 수도 있다.  버그를 방지하고 싶으면useEffect에서 타이머 만들기 전에 기존 타이머를

제거되도록 만든다. 타이머 제거하고 싶으면 return ()=>{} 안에 clearTimeout(타이머) 이렇게 코드짜면 된다.