[Redux] 장바구니 기능 구현

김병화·2023년 8월 24일
0

Arch

목록 보기
14/14

1. authSlice reducer 추가

// authSlice.ts

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { User } from './usersSlice';

interface AuthState {
  isLogin: boolean;
  user: User;
}

const initialState: AuthState = {
  isLogin: false,
  user: {
    id: '',
    pw: '',
    cart: [],
  },
};

const authSlice = createSlice({
  name: 'auth',
  initialState: initialState,
  reducers: {
    login: (state, action: PayloadAction<User>) => {
      state.isLogin = true;
      state.user = action.payload;
    },
    logout: (state) => {
      state.isLogin = false;
      state.user = {
        id: '',
        pw: '',
        cart: [],
      };
    },

    addCart: (state, action: PayloadAction<number>) => {
      if (state.isLogin && state.user.cart) {
        state.user.cart.push({ productId: action.payload }); // (productId: 인자로 받은 payload)를 push한다.
      }
    },

    removeCart: (state, action: PayloadAction<number>) => {
      if (state.isLogin && state.user.cart) {
        state.user.cart = state.user.cart.filter((product) => product.productId !== action.payload);
      }
    },
  },
});

export const { login, logout, addCart, removeCart } = authSlice.actions;

export default authSlice.reducer;

addCartremoveCart를 추가해준다.




2. 장바구니 추가 기능

// GoodsCard.tsx

function GoodsCard({ id, img, title, artist, price }: GoodsCardProps): React.ReactElement 

  // other codes...

  const handleCartButton = (isLogin: boolean, e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    if (!isLogin) {
      Swal.fire({
        title: '로그인 후 장바구니 이용 가능합니다.',
        confirmButtonText: '확인',
        confirmButtonColor: '#0E1B4E',
        customClass: {
          popup: 'swal-popup',
          title: 'swal-title',
        },
        scrollbarPadding: false,
      });
    } else {
      const isExist = user.cart?.some((list) => {
        return list.productId === id;
      });

      if (isExist) {
        Swal.fire({
          title: '이미 장바구니에 담긴 상품입니다.',
          confirmButtonText: '확인',
          confirmButtonColor: '#0E1B4E',
          customClass: {
            popup: 'swal-popup',
            title: 'swal-title',
          },
          scrollbarPadding: false,
        });
      } else {
        dispatch(addCart(id));

        Swal.fire({
          title: '장바구니에 정상적으로 추가되었습니다.',
          confirmButtonText: '장바구니로 이동',
          confirmButtonColor: '#0E1B4E',
          showCancelButton: true,
          cancelButtonText: '쇼핑 계속하기',
          cancelButtonColor: '#0E1B4E',
          focusCancel: true,
          customClass: {
            popup: 'swal-popup',
            title: 'swal-title',
          },

          scrollbarPadding: false,
        }).then((result) => {
          if (result.isConfirmed) {
            navigate('/cart');
          }
        });
      }
    }
  };

개별 상품의 장바구니 추가 버튼 클릭 시

  1. 로그인 여부 체크

  2. 이미 추가한 상품인지 체크

  3. 위 과정 모두 통과 시 dispatch(addCart(id));




3. 장바구니 페이지 렌더링

// Cart.tsx

function Cart() {
  const user = useAppSelector((state) => state.auth.user) as User;
  const cartProductsId = user.cart;

  // 방법 1. goods의 id 순서대로 - some()
  // const cart = goods.filter((e) => {
  //   return cartProductsId?.some((cartProductId) => cartProductId.productId === e.id);
  // });

  // 방법 2. 장바구니에 추가한 순서대로 - find()
  const cartList = (cartProductsId || [])
    .map((productId) => {
      return goods.find((e) => e.id === productId.productId);
    })
    // 타입 오류 방지를 위해 undefined 제거
    .filter((product): product is (typeof goods)[number] => product !== undefined);

  return (
    <Inner>
      <PageTitle korean>장바구니</PageTitle>
      {cartList.length > 0 ? (
        <>
          <ProductWrapper>
            {cartList.map((product) => (
              <CartProduct
                key={product.id}
                id={product.id}
                img={product.img}
                title={product.title}
                artist={product.artist}
                price={product.price}
              />
            ))}
          </ProductWrapper>
          <OrderWrapper>
            <OrderButton type='submit' variant='contained'>
              주문하기
            </OrderButton>
          </OrderWrapper>
        </>
      ) : (
        <NoProduct>장바구니에 담긴 상품이 존재하지 않습니다.</NoProduct>
      )}
    </Inner>
  );
}

방법은 두 가지.


Way 1
some()을 이용하여 전체 상품(goods) 중 user의 cart 배열에 있는 id(cartProductsId)와 일치하는 상품을 true/false로 반환 후 filter()


Way 2

user의 cart 배열에 있는 id(cartProductsId)에 전체 상품(goods) 기준으로 map()을 돌려서 id가 일치하는 것을 find()


나는 두 번째 방법을 사용.

이 때, undefined 타입 오류가 발생하여 filter()를 통해 undefined 요소를 모두 제외할 수 있도록 함.




4. 장바구니 삭제 기능

// CartProductCard.tsx

function CartProductCard({ id, img, title, artist, price }: CartProductProps): React.ReactElement {
  const dispatch = useAppDispatch();

  const [count, setCount] = useState(1);
  const [sumPrice, setSumPrice] = useState(price);

  const removeProduct = (id: number) => {
    dispatch(removeCart(id));
  };

  const handlePrice = (number: number) => {
    let price = number.toLocaleString();
    return price;
  };

  const handleCount = (action: string) => {
    if (action === 'minus' && count > 1) {
      setCount((prev) => prev - 1);
    } else if (action === 'plus') {
      setCount((prev) => prev + 1);
    }
  };

  useEffect(() => {
    setSumPrice(price * count);
  }, [count, price]);

  return (
    <Container>
      <ImgWrapper>
        <img src={`${img}`} alt={`${title} by ${artist}`}></img>
      </ImgWrapper>
      <ProductInfo>
        <Title>{title}</Title>
        <Artist>{artist}</Artist>
      </ProductInfo>
      <CounterContainer>
        <IconButton size='large' onClick={() => handleCount('minus')}>
          <RemoveCircleOutlineIcon />
        </IconButton>
        <Counter>
          <strong>{count}</strong>
        </Counter>
        <IconButton size='large' onClick={() => handleCount('plus')}>
          <AddCircleOutlineIcon />
        </IconButton>
      </CounterContainer>
      <Price>
        <strong>{handlePrice(sumPrice)}</strong></Price>
      <IconButton size='large' onClick={() => removeProduct(id)}>
        <ClearIcon />
      </IconButton>
    </Container>
  );
}

삭제 버튼 만들어주고 onClickdispatch(removeCart(id));

action을 활용하여 수량 증가/감소를 하나의 함수에서 제어

0개의 댓글

관련 채용 정보