📘 [TODO] 쇼핑몰 애플리케이션의 주요 기능 구현
- [장바구니 담기] 버튼을 이용해 장바구니에 해당 상품이 추가
- 장바구니 내 [삭제] 버튼을 이용해 장바구니의 상품이 제거
- 장바구니 내에서 각 아이템 개수를 변경
- 장바구니의 상품 개수의 변동이 생길 때마다, 상단 내비게이션 바에 상품 개수가 업데이트
✨ Intro | 컴포넌트 구조와 데이터 흐름 파악을 위한 그림 그리기
🖥️ App.js
import React, { useState } from 'react';
import Nav from './components/Nav';
import ItemListContainer from './pages/ItemListContainer';
import './App.css';
import './variables.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 cartcnt={cartItems.length} />
<Routes>
{/* ItemListContainer의 items은 useState의 현재 itmes들을 나타내고
useState의 items는 initialState.items를 가지고 오고 있다.
initialState{
"items": [ ItemListContainer에 전달 => 상품에 대한 정보
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900}
],
"cartItems":[ cartItems에 전달 => 장바구니에 담은 아이템 Id와 수량
{
"itemId": 1,
"quantity": 1
}
]
} */}
<Route
path="/"
element={
<ItemListContainer
items={items}
cartItems={cartItems}
setCartItems={setCartItems}
/>
}
/>
<Route
path="/shoppingcart"
element={
<ShoppingCart
cartItems={cartItems}
items={items}
setCartItems={setCartItems}
/>
}
/>
</Routes>
<img
id="logo_foot"
src={`${process.env.PUBLIC_URL}/codestates-logo.png`}
alt="logo_foot"
/>
</Router>
);
}
export default App;
🖥️ ItemListContainer.js
import React from 'react';
import Item from '../components/Item';
//! items로 가져온 것은 배열 형태임
{
/*"items": [ => 상품에 대한 정보
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900}
...
}
], */
}
function ItemListContainer({ items, cartItems, setCartItems }) {
const handleClick = (itemId) => {
// 기존 장바구니의 아이템 아이디가 새로 클릭한 아이템 아이디와 같은 경우는
// 이미 장바구니의 아이템이 존재한다는 것이므로 존재하지 않을 때만 추가해 준다.
let itemIdx = cartItems.findIndex((ele) => ele.itemId === itemId);
if (!cartItems.filter((ele) => ele.itemId === itemId)[0]) {
setCartItems([
...cartItems,
{
itemId: itemId,
quantity: 1,
},
]);
} else {
//이미 있는 경우, 해당 인덱스 증감시켜주기
cartItems[itemIdx].quantity += 1;
setCartItems([...cartItems]);
}
};
return (
<div id="item-list-container">
<div id="item-list-body">
<div id="item-list-title">쓸모없는 선물 모음</div>
{/* 배열 items를 Props로 전달
Item에서 item은 ites 요소 하나하나이며, Key는 인덱스 번호 */}
{items.map((item, idx) => (
<Item item={item} key={idx} handleClick={handleClick} />
))}
</div>
</div>
);
}
export default ItemListContainer;
🖥️ Item.js
import React from 'react';
//! 요소 하나하나씩 받고 있음
{
/*"items": [ => 상품에 대한 정보
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900}
...
}
], */
}
export default function Item({ item, handleClick }) {
return (
<div key={item.id} className="item">
<img className="item-img" src={item.img} alt={item.name}></img>
<span className="item-name">{item.name}</span>
<span className="item-price">{item.price}</span>
<button className="item-button" onClick={() => handleClick(item.id)}>
장바구니 담기
</button>
</div>
);
}
import React, { useState } from 'react';
import CartItem from '../components/CartItem';
import OrderSummary from '../components/OrderSummary';
//! items로 가져온 것은 배열 형태임
{
/*"items": [ => 상품에 대한 정보
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900}
...
}
], */
}
//! cartItems로 가져온 것은 배열 형태임
{
/*
"cartItems":[ cartItems에 전달 => 장바구니에 담은 아이템 Id와 수량
{
"itemId": 1,
"quantity": 1
}
]
*/
}
export default function ShoppingCart({ items, cartItems, setCartItems }) {
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) => {
setCartItems(
cartItems.map((ele) => {
if (ele.itemId === itemId) {
return {
itemId: itemId,
quantity: quantity,
};
} else {
return ele;
}
})
);
};
//! [삭제] 버튼 누르면 장바구니의 상품이 제거
const handleDelete = (itemId) => {
setCheckedItems(checkedItems.filter((el) => el !== itemId));
// 기존 카트에 있는 아이템의 itemId와 삭제버튼을 누른 itemId가 같지 않은 것만 뽑아오기
setCartItems(cartItems.filter((ele) => ele.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) {
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>
);
}
🖥️ Nav.js
import React from 'react';
import Item from '../components/Item';
//! items로 가져온 것은 배열 형태임
{
/*"items": [ => 상품에 대한 정보
{
"id": 1,
"name": "노른자 분리기",
"img": "../images/egg.png",
"price": 9900}
...
}
], */
}
function ItemListContainer({ items, cartItems, setCartItems }) {
const handleClick = (itemId) => {
// 기존 장바구니의 아이템 아이디가 새로 클릭한 아이템 아이디와 같은 경우는
// 이미 장바구니의 아이템이 존재한다는 것이므로 존재하지 않을 때만 추가해 준다.
let itemIdx = cartItems.findIndex((ele) => ele.itemId === itemId);
if (!cartItems.filter((ele) => ele.itemId === itemId)[0]) {
setCartItems([
...cartItems,
{
itemId: itemId,
quantity: 1,
},
]);
} else {
//이미 있는 경우, 해당 인덱스 증감시켜주기
cartItems[itemIdx].quantity += 1;
setCartItems([...cartItems]);
}
};
return (
<div id="item-list-container">
<div id="item-list-body">
<div id="item-list-title">쓸모없는 선물 모음</div>
{/* 배열 items를 Props로 전달
Item에서 item은 ites 요소 하나하나이며, Key는 인덱스 번호 */}
{items.map((item, idx) => (
<Item item={item} key={idx} handleClick={handleClick} />
))}
</div>
</div>
);
}
export default ItemListContainer;