Cmarket Redux 과제

햄은 개발 공부중·2023년 2월 27일
0
post-thumbnail

학습목표 ✏️

  • Redux의 주요 개념인 Action, Dispatch, Reducer, Store를 작성하고 연결하여 사용
  • Redux hooks(useSelector, useDispatch)를 사용해 Store를 업데이트

과제

< Shopping Cart Actions >

  • addToCart는 ADD_TO_CART 액션을 생성해야 한다.
  • removeFromCar는 REMOVE_FROM_CART 액션을 생성해야 한다
  • setQuantity는 SET_QUANTITY 액션을 생성해야 한다

< Item Reducer >

  • ADD_TO_CART 액션에 따라 cartItem 상태가 변해야 한다
  • REMOVE_FROM_CART 액션에 따라 cartItems의 상태가 변해야 한다
  • SET_QUANTITY 액션에 따라 cartItems의 상태가 변해야 한다
  • 리듀서는 다른 상태의 값을 보존해야 한다

< Shopping pages >

  • ShoppingCart에 cartItems가 렌더되어야 한다
  • ADD_TO_CART 액션에 따라 OrderSummary가 렌더되어야 한다
  • REMOVE_FROM_CART 액션에 따라 ShoppingCart가 렌더되어야 한다
  • SET_QUANTITY 액션에 따라 OrderSummary가 렌더되어야 한다
  • Checkbox의 상태에 따라 OrderSummary가 렌더되어야 한다

과제 코드

index.js

export const ADD_TO_CART = "ADD_TO_CART";
export const REMOVE_FROM_CART = "REMOVE_FROM_CART";
export const SET_QUANTITY = "SET_QUANTITY";
export const NOTIFY = "NOTIFY";
export const ENQUEUE_NOTIFICATION = "ENQUEUE_NOTIFICATION";
export const DEQUEUE_NOTIFICATION = "DEQUEUE_NOTIFICATION";

// actions creator functions
export const addToCart = (itemId) => {
  return {
    type: ADD_TO_CART,
    payload: {
      quantity: 1,
      itemId,
    },
  };
};

export const removeFromCart = (itemId) => {
  return {
    //TODO
    type: REMOVE_FROM_CART,
    payload: { itemId },
  };
};

export const setQuantity = (itemId, quantity) => {
  return {
    //TODO
    type: SET_QUANTITY,
    payload: {
      quantity,
      itemId,
    },
  };
};

export const notify =
  (message, dismissTime = 5000) =>
  (dispatch) => {
    const uuid = Math.random();
    dispatch(enqueueNotification(message, dismissTime, uuid));
    setTimeout(() => {
      dispatch(dequeueNotification());
    }, dismissTime);
  };

export const enqueueNotification = (message, dismissTime, uuid) => {
  return {
    type: ENQUEUE_NOTIFICATION,
    payload: {
      message,
      dismissTime,
      uuid,
    },
  };
};

export const dequeueNotification = () => {
  return {
    type: DEQUEUE_NOTIFICATION,
  };
};

ShoppingCart.js

import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { removeFromCart, setQuantity } from "../actions";
import CartItem from "../components/CartItem";
import OrderSummary from "../components/OrderSummary";

export default function ShoppingCart() {
  const state = useSelector((state) => state.itemReducer);
  const { cartItems, items } = state;
  const dispatch = useDispatch();
  const [checkedItems, setCheckedItems] = useState(
    cartItems.map((el) => el.itemId)
  );

  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) => {
    //TODO: dispatch 함수를 호출하여 액션을 전달하세요.
    dispatch(setQuantity(itemId, quantity));
  };

  const handleDelete = (itemId) => {
    setCheckedItems(checkedItems.filter((el) => el !== itemId));
    //TODO: dispatch 함수를 호출하여 액션을 전달하세요.
    dispatch(removeFromCart(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) {
        let quantity = cartItems[i].quantity;
        let price = items.filter((el) => el.id === cartItems[i].itemId)[0]
          .price;

        total.price = total.price + quantity * price;
        total.quantity = total.quantity + quantity;
      }
    }
    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>
  );
}

ItemListContainer.js

import React from "react";
import { addToCart, notify } from "../actions/index";
import { useSelector, useDispatch } from "react-redux";
import Item from "../components/Item";

function ItemListContainer() {
  const state = useSelector((state) => state.itemReducer);
  const { items, cartItems } = state;
  const dispatch = useDispatch();

  const handleClick = (item) => {
    if (!cartItems.map((el) => el.itemId).includes(item.id)) {
      //TODO: dispatch 함수를 호출하여 아이템 추가에 대한 액션을 전달하세요.
      dispatch(addToCart(item.id));
      dispatch(notify(`장바구니에 ${item.name}이(가) 추가되었습니다.`));
    } else {
      dispatch(notify("이미 추가된 상품입니다."));
    }
  };

  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(item);
            }}
          />
        ))}
      </div>
    </div>
  );
}

export default ItemListContainer;

ItemReducer.js

import { REMOVE_FROM_CART, ADD_TO_CART, SET_QUANTITY } from "../actions/index";
import { initialState } from "./initialState";

const itemReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TO_CART:
      //TODO 추가
      return Object.assign({}, state, {
        cartItems: [...state.cartItems, action.payload],
      });

      break;
    case REMOVE_FROM_CART:
      //TODO
      let del = state.cartItems.filter(
        (el) => el.itemId !== action.payload.itemId
      );
      return Object.assign({}, state, {
        cartItems: del,
      });

      break;
    case SET_QUANTITY:
      let idx = state.cartItems.findIndex(
        (el) => el.itemId === action.payload.itemId
      );
      //TODO
      return Object.assign({}, state, {
        cartItems: [
          ...state.cartItems.slice(0, idx),
          action.payload,
          ...state.cartItems.slice(idx + 1),
        ],
      });

      break;
    default:
      return state;
  }
};

export default itemReducer;

느낀점 😲

첫날 페어님과 함께 이론공부를 한 후 바로 문제를 보니 어느정도 문제가 해결이 됐지만 아직 이해가 안 가는 코드가 몇몇 있어서 다시 복습을 해아할 것 같다!
그리고 줌수업을 들으면서 느낀건데 문제를 클론하고 폴더와 파일들 구조를 먼저 파악하는 습관을 가져야겠단 생각이 들었다. 문서들의 구조를 알고(컴포넌트 폴더, 리듀서파일 등등) 문제에 접근하니 문제를 파악하기 더 수월해지는 느낌이었다! 앞으로 과제에서는 시작 전에 꼭 문서구조를 파악하고 시작해야겠다!

profile
내가 보려고 정리하는 블로그🔥

0개의 댓글