이번에는 본격 상품 리스트에 있는 상품을 클릭하여 장바구니에 넣어보도록 하겠습니다.
자료출처 : https://brand.naver.com/linefriends/category/b4f7430d419e4e729c8eae3093b37e21?cp=1
저작권 : 라인 프렌즈
위 데이터는 공부 목적으로 사용했으며 어떠한 결제 시스템이 없음을 사전에 공지합니다
- 상품 리스트에 있는 상품을 클릭하면 장바구니에 상품을 추가한다.
- 장바구니에 담긴 총 아이템 갯수를 오른쪽 상단 뱃지에 출력한다.
- 장바구니에 담긴 상품 리스트를 출력한다.
- 장바구니 리스트에서 상품의 갯수를 추가하거나 제거할 수 있다.
앱 전반적으로 상품을 추가, 제거하거나 장바구니 총 갯수를 가져오기 위해 글로벌 함수를 만들어 보겠습니다.
import React, {useState} from 'react';
import {database} from "./database/products";
import {ShoppingCartItem} from "./interfaces/item.interface";
import {AppRouter} from "./routers/AppRoutes";
import './App.css'
// SET CATEGORY
const arr = database.map((item) => {
return item.category
});
arr.push('ALL');
const category = Array.from(new Set(arr)).sort();
const App = () => {
// NOTE - 1
const [cartItem, setCartItem] = useState<ShoppingCartItem[]>([]);
// NOTE - 2
const handleAddToCart = (clickedItem: ShoppingCartItem) => {
setCartItem((prev) => {
const isItemInCart = cartItem.find((item) => item.id === clickedItem.id);
if (isItemInCart) {
return prev.map((item) => (item.id === clickedItem.id ? {...item, amount: item.amount + 1} : item));
}
return [{...clickedItem, amount: 1}, ...prev];
});
};
// NOTE - 3
const handleRemoveFromCart = (id: number) => {
setCartItem((prev) =>
prev.reduce((acc, item) => {
if (item.id === id) {
if (item.amount === 1) return acc;
return [...acc, {...item, amount: item.amount - 1}]
} else {
return [...acc, item]
}
}, [] as ShoppingCartItem[])
)
}
// NOTE - 4
const getTotalItems = (items: ShoppingCartItem[] | null) => {
return items?.reduce((acc, item) => acc + item.amount, 0)
}
return (
<>
<AppRouter
menuList={database}
category={category}
cartItem={cartItem}
addToCart={handleAddToCart}
removeFromCart={handleRemoveFromCart}
getTotalItem={getTotalItems}/>
</>
);
}
export default App;
💡 NOTE - 1
앱 전반적으로 사용할 장바구니 리스트 입니다.
💡 NOTE - 2
handleAddToCart
장바구니에 상품을 추가하는 함수입니다.
상품을 클릭하면 상품 id가 기존의 상품 리스트에 있는지 확인합니다.
기존에 상품이 없으면 amount = 1로, 있으면 amount += 1 합니다.
💡 NOTE - 3
handleRemoveFromCart
장바구니 리스트에서 상품을 제거하는 함수입니다.
함수를 실행할 때마다 amount 값을 줄이고, amount = 0 이 되면 장바구니 리스트에서 제거합니다.
💡 NOTE - 4
getTotalItems
총 장바구니에 담긴 상품 갯수를 가져오는 함수입니다.
각 함수들을 이제 AppRouter
에 전달하겠습니다.
import React from "react";
import {BrowserRouter, Routes, Route} from "react-router-dom";
import {Main} from "../pages/main";
import {MenuListInterface} from "../interfaces/item.interface";
import {Navbar} from "../components/Navbar";
import {Home} from "../pages/Home";
import {Header} from "../components/Header";
type AppRoutesType = {
menuList: MenuListInterface[] | null
category: string[] | null
cartItem: ShoppingCartItem[] | null
addToCart: (item: ShoppingCartItem) => void
removeFromCart: (id: number) => void
getTotalItem: (items: any) => any
}
export const AppRouter = ({menuList, category, cartItem, addToCart, removeFromCart, getTotalItem}: AppRoutesType) => {
return <>
<BrowserRouter>
{/*NOTE - 5*/}
<Header cartItem={cartItem} addToCart={addToCart} removeFromCart={removeFromCart} getTotalItem={getTotalItem}/>
<Navbar category={category}/>
<Routes>
<Route path={'/'} element={<Home/>}></Route>
<Route path={'/:category'} element={<Main menuList={menuList}/>}></Route>
</Routes>
</BrowserRouter>
</>
}
💡 NOTE - 5
아직 헤더를 만들진 않았지만 미리 헤더 컴포넌트에 필요한 값을 전달하도록 하겠습니다.
import React, {useState} from "react";
// INTERFACES
import {ShoppingCartItem} from "../interfaces/item.interface";
import {HeaderWrap} from "../styles/header.styled,ts";
import {Drawer} from "@mui/material";
import SearchIconfrom "@mui/icons-material/Search";
import ShoppingBasketOutlinedIconfrom "@mui/icons-material/ShoppingBasketOutlined";
import ViewHeadlineIconfrom "@mui/icons-material/ViewHeadline";
import Badgefrom "@mui/material/Badge";
type HeaderType = {
cartItem: ShoppingCartItem[] | null
addToCart: (item: ShoppingCartItem) => void
removeFromCart: (id: number) => void
getTotalItem: (items: any) => any
}
export const Header = ({cartItem, addToCart, removeFromCart, getTotalItem}: HeaderType) => {
const [cartOpen, setCartOpen] = useState<boolean>(false);
return <HeaderWrap>
<Drawer anchor={'right'} open={cartOpen} onClose={() => setCartOpen(false)}>
</Drawer>
<div className={'header-left'}>
<div>
<p>R</p>
</div>
<div className={'logo'}>
RINE FRIENDS
</div>
</div>
<div className={'header-right'}>
<div>
<SearchIcon/>
</div>
<div onClick={() => setCartOpen(true)}>
<Badge className={'count_badge'} badgeContent={getTotalItem(cartItem)} color={'error'}></Badge>
<ShoppingBasketOutlinedIcon/>
</div>
<div>
<ViewHeadlineIcon/>
</div>
</div>
</HeaderWrap>
}
장바구니 아이콘을 클릭하니 이벤트가 발생하는 것을 볼 수 있습니다.
이제 장바구니 아이콘을 클릭했을 때 장바구니 리스트가 출력하도록 해보겠습니다.
상품 목록 리스트를 만들 때와 마찬가지로 상품 하나 당 출력되는 컴포넌트 먼저 만들어보겠습니다.
import React from "react";
import {numberFormat} from "../common";
// INTERFACES
import {ShoppingCartItem} from "../interfaces/item.interface";
// CSS
import {CartItemWrap} from "../styles/cartItem.styled";
import { Button } from "@mui/material";
type CartItemType = {
item: ShoppingCartItem
addToCart: (item: ShoppingCartItem) => void
removeFromCart: (id: number) => void
}
export const CartItem = ({item, addToCart, removeFromCart}: CartItemType) => {
return <CartItemWrap>
<div className="thumb">
<img src={item.image} alt={item.title}></img>
</div>
<div className="info">
<p>{item.title}</p>
<p>Price : {numberFormat(item.price)}원</p>
<p>Total : {numberFormat(item.amount * item.price)}원</p>
</div>
<div className="buttons">
<Button
size="small"
disableElevation
variant="contained"
onClick={() => {
removeFromCart(item.id);
}}
>
-
</Button>
{item.amount}
<Button
size="small"
disableElevation
variant="contained"
onClick={() => {
addToCart(item);
}}
>
+
</Button>
</div>
</CartItemWrap>
}
결과
나중에 데이터를 받으면 다음과 같이 출력합니다.
이제 상품 리스트 만들어 보겠습니다.
나중에 헤더에서 클릭한 장바구니 리스트 (cartItem)를 받으면 반복문을 돌면서 위에서 만든 CartItem
컴포넌트에 값을 전달하겠습니다.
import React from "react";
import {numberFormat} from "../common";
import {CartListWrap} from "../styles/cartList.styled";
import {ShoppingCartItem} from "../interfaces/item.interface";
import {CartItem} from "./CartItem";
type CartList = {
cartItem: ShoppingCartItem[] | null
addToCart: (item: ShoppingCartItem) => void
removeFromCart: (id: number) => void
}
export const CartList = ({cartItem, addToCart, removeFromCart}: CartList) => {
{/* NOTE - 6 */}
const totalPrice = (items: ShoppingCartItem[]) => {
return items?.reduce((count, item) => count + item.amount * item.price, 0);
}
const hidden = cartItem?.length === 0 ? 'hidden' : "";
return <CartListWrap>
<div className={'title'}>
<h3>Your Shopping Cart</h3>
</div>
<div className={hidden}>
{cartItem?.map((item) => (
<CartItem key={item.id} item={item} addToCart={addToCart} removeFromCart={removeFromCart} />
))}
<div className={'price'}>
<span>
TOTAL : <strong>{numberFormat(totalPrice(cartItem? cartItem : []))}원</strong>
</span>
</div>
</div>
</CartListWrap>
}
💡 NOTE - 6 -
totalPrice
장바구니에 담긴 모든 상품의 최종 가격을 출력합니다.
CartList가 만들어졌으니 헤더에 추가하겠습니다.
import React, {useState} from "react";
// COMPONENTS
import {CartList} from "./CartList";
// INTERFACES
import {ShoppingCartItem} from "../interfaces/item.interface";
import {HeaderWrap} from "../styles/header.styled,ts";
import {Drawer} from "@mui/material";
import SearchIcon from "@mui/icons-material/Search";
import ShoppingBasketOutlinedIcon from "@mui/icons-material/ShoppingBasketOutlined";
import ViewHeadlineIcon from "@mui/icons-material/ViewHeadline";
import Badge from "@mui/material/Badge";
type HeaderType = {
cartItem: ShoppingCartItem[] | null
addToCart: (item: ShoppingCartItem) => void
removeFromCart: (id: number) => void
getTotalItem: (items: any) => any
}
export const Header = ({cartItem, addToCart, removeFromCart, getTotalItem}: HeaderType) => {
const [cartOpen, setCartOpen] = useState<boolean>(false);
return <HeaderWrap>
<Drawer anchor={'right'} open={cartOpen} onClose={() => setCartOpen(false)}>
{/* NOTE - 7 */}
<CartList cartItem={cartItem} addToCart={addToCart} removeFromCart={removeFromCart} />
</Drawer>
<div className={'header-left'}>
<div>
<p>R</p>
</div>
<div className={'logo'}>
RINE FRIENDS
</div>
</div>
<div className={'header-right'}>
<div>
<SearchIcon/>
</div>
<div onClick={() => setCartOpen(true)}>
<Badge className={'count_badge'} badgeContent={getTotalItem(cartItem)} color={'error'}></Badge>
<ShoppingBasketOutlinedIcon/>
</div>
<div>
<ViewHeadlineIcon/>
</div>
</div>
</HeaderWrap>
}
💡 NOTE - 7
CartList
컴포넌트를 추가하고 필요한 인자들을 전달하겠습니다.
자 이제 장바구니가 만들어졌습니다.
이제 장바구니에 아이템을 추가해 보도록 하겠습니다.
import React from "react";
import {ShoppingCartItem} from "../interfaces/item.interface";
import Swal from "sweetalert2";
import {numberFormat} from "../common";
// CSS
import {Button} from "@mui/material";
import {ItemWrap} from "../styles/item.styled,ts";
type ItemType = {
item: ShoppingCartItem
addToCart: (item: ShoppingCartItem) => void
}
export const Item = ({item,addToCart}: ItemType) => {
const onClick = (clickedItem : ShoppingCartItem) => {
Swal.fire({
title:"장바구니에 추가하시겠습니까?",
showDenyButton:true,
confirmButtonText: "ADD",
denyButtonText:"CANCLE"
}).then((result) => {
if(result.isConfirmed) {
addToCart(clickedItem)
Swal.fire('추가했습니다.', "", "success");
} else {
Swal.fire('취소했습니다.', "", "error");
}
})
}
return <ItemWrap>
<div>
<div className="container">
<div className="item-image">
<img src={item.image} alt={item.title}/>
</div>
<div className="item-info">
<h3>{item.title}</h3>
<h3>{numberFormat(item.price)}원</h3>
</div>
<Button
onClick={() => {
onClick(item);
}}
>
Add To Cart
</Button>
</div>
</div>
</ItemWrap>
}
import React from "react";
import {BrowserRouter, Routes, Route} from "react-router-dom";
// PAGES
import {Main} from "../pages/main";
import {Home} from "../pages/Home";
// COMPONENTS
import {Navbar} from "../components/Navbar";
import {Header} from "../components/Header";
// INTERFACES
import {MenuListInterface, ShoppingCartItem} from "../interfaces/item.interface";
type AppRoutesType = {
menuList: MenuListInterface[] | null
category: string[] | null
cartItem: ShoppingCartItem[] | null
addToCart: (item: ShoppingCartItem) => void
removeFromCart: (id: number) => void
getTotalItem: (items: any) => any
}
export const AppRouter = ({menuList, category, cartItem, addToCart, removeFromCart, getTotalItem}: AppRoutesType) => {
return <>
<BrowserRouter>
<Header cartItem={cartItem} addToCart={addToCart} removeFromCart={removeFromCart} getTotalItem={getTotalItem}/>
<Navbar category={category}/>
<Routes>
<Route path={'/'} element={<Home/>}></Route>
{/* NOTE - 1*/}
<Route path={'/:category'} element={<Main menuList={menuList} addToCart={addToCart}/>}></Route>
</Routes>
</BrowserRouter>
</>
}
💡 NOTE - 8
Main
컴포넌트에 addToCart 를 전달하겠습니다.
리액트로 장바구니 페이지 만들기가 끝났습니다.
다음에도 간단한 리액트 프로젝트로 찾아오겠습니다.