[React] 장바구니 페이지 만들기 프로젝트 (2)

Kylie·2022년 11월 29일
0

[React] Shopping cart

목록 보기
2/2
post-thumbnail

1. 들어가기 전

이번에는 본격 상품 리스트에 있는 상품을 클릭하여 장바구니에 넣어보도록 하겠습니다.

자료출처 : https://brand.naver.com/linefriends/category/b4f7430d419e4e729c8eae3093b37e21?cp=1
저작권 : 라인 프렌즈
위 데이터는 공부 목적으로 사용했으며 어떠한 결제 시스템이 없음을 사전에 공지합니다


2.목표

  1. 상품 리스트에 있는 상품을 클릭하면 장바구니에 상품을 추가한다.
  2. 장바구니에 담긴 총 아이템 갯수를 오른쪽 상단 뱃지에 출력한다.
  3. 장바구니에 담긴 상품 리스트를 출력한다.
  4. 장바구니 리스트에서 상품의 갯수를 추가하거나 제거할 수 있다.

3. handle 함수 만들기

앱 전반적으로 상품을 추가, 제거하거나 장바구니 총 갯수를 가져오기 위해 글로벌 함수를 만들어 보겠습니다.

src/App.tsx

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에 전달하겠습니다.

src/routers/AppRoutes.tsx

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
아직 헤더를 만들진 않았지만 미리 헤더 컴포넌트에 필요한 값을 전달하도록 하겠습니다.


4. header 만들기

src/components/Header.tsx

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>
}

장바구니 아이콘을 클릭하니 이벤트가 발생하는 것을 볼 수 있습니다.

이제 장바구니 아이콘을 클릭했을 때 장바구니 리스트가 출력하도록 해보겠습니다.


5. 장바구니 리스트 만들기

상품 목록 리스트를 만들 때와 마찬가지로 상품 하나 당 출력되는 컴포넌트 먼저 만들어보겠습니다.

src/components/CartItem.tsx

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 컴포넌트에 값을 전달하겠습니다.

src/components/CartList.tsx

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가 만들어졌으니 헤더에 추가하겠습니다.

src/components/Header.tsx

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 컴포넌트를 추가하고 필요한 인자들을 전달하겠습니다.

자 이제 장바구니가 만들어졌습니다.
이제 장바구니에 아이템을 추가해 보도록 하겠습니다.


6. 장바구니에 상품 추가하기

src/components/Item.tsx

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>
}

src/AppRoutes.tsx

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 를 전달하겠습니다.


7. 완성

리액트로 장바구니 페이지 만들기가 끝났습니다.
다음에도 간단한 리액트 프로젝트로 찾아오겠습니다.

전체코드 보기

profile
올해보단 낫겠지....

0개의 댓글