✅ useState 를 이용해 상태를 사용하는 방법을 학습합니다.
✅ [장바구니 담기] 버튼을 이용해 장바구니에 해당 상품이 추가되도록 구현하세요.
✅ 장바구니 내 [삭제] 버튼을 이용해 장바구니의 상품이 제거되도록 구현하세요.
✅ 장바구니의 상품 갯수의 변동이 생길 때마다, 상단 내비게이션 바에 상품 갯수가 업데이트되도록 구현하세요.
이미 어느정도 완성되어있는 쇼핑몰 홈페이지가 있었고 여기서
장바구니 담기, 삭제, 상품갯수 업데이트기능을 작동하도록 구현하면 됐다
제일 상위폴더인 app.js에 이미 작성되어있는 아이템들의 정보와 카트에 담겨있는 아이템의 정보를 각 컴포넌트에 내려줘서 문제를 해결 할 수 있다
const [items, setItems] = useState(initialState.items);
const [cartItems, setCartItems] = useState(initialState.cartItems);
이미 각 컴포넌트에 handleClick들이 이름만 작성이 되어 있어서
그곳을 채워넣는 형태로 구현해 줬다
여기서 중요한것은
setItems
setCartItems
변경하는 함수들도 props로 넘겨줄 수 있다는 것!
import React, { useState } from 'react';
import Nav from './components/Nav';
import ItemListContainer from './pages/ItemListContainer';
import './App.css';
import {
BrowserRouter as Router,
Switch,
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 cartItems={cartItems}/>
<Switch>
<Route exact={true} path="/">
// cartItems={cartItems} setCartItems={setCartItems}을 props로 넘겨준다
<ItemListContainer items={items} cartItems={cartItems} setCartItems={setCartItems }/>
</Route>
<Route path="/shoppingcart">
<ShoppingCart cartItems={cartItems} items={items} setCartItems={setCartItems}/>
</Route>
</Switch>
</Router>
);
}
export default App;
ItemListContainer의 handleClick을 item 컴포넌트에서 받아서 사용해 장바구니에 추가해주는 형태였다
이때 item의 장바구니 담기 onClick에 onClick={(e) => handleClick(e, item.id)}
이미 인자가 두개가 작성이 되어 있는걸 보고 시작해야 한다
장바구니에 담기를 구현할때는 고려해줘야 할 사항이 몇가지 있었는데
장바구니에 없는 아이템은 추가해주고
있는 아이템이면 수량만 추가해줘야 했다
import React from 'react';
import Item from '../components/Item';
function ItemListContainer({ items, cartItems, setCartItems }) {
const handleClick = ( e, id ) => {
let newCartItem = {};
newCartItem.itemId = id;
newCartItem.quantity = 1;
for(let i = 0; i < cartItems.length; i++){
if(cartItems[i].itemId === id){
setCartItems([...cartItems])
cartItems[i].quantity++
}else{
setCartItems([...cartItems, newCartItem])
}
}
}
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} />)}
</div>
</div>
);
}
export default ItemListContainer;
나는 포문을 돌려서 해결했는데 페어님은 findIndex를 사용해서 구현한 코드가 인상적이었다
const handleClick = (e, Id) => {
let original= cartItems
let findNumber = cartItems.findIndex((e)=>e.itemId===Id)
if(findNumber!==-1){
original[findNumber].quantity+=1
setCartItems(original)
}else {
setCartItems ([...original,
{"itemId": Id,"quantity": 1}])
}
클릭을 하면 클릭한 값의 id를 가져온다.
클릭한 값과 현재 있는 값의 id 를 비교해서 있으면 그냥 추가만 하고
없으면 새로운 값을 추가해준다.
import React from 'react'
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={(e) => handleClick(e, item.id)}>장바구니 담기</button>
</div>
)
}
handleDelete함수를 수정해서 사용하면 된다
setCartItems(cartItems.filter((el)=> el.itemId !== itemId))
cartItems에 필터를 돌려서 cartItems의 el은 객체형태로 되어있으므로 el.itemId와 클릭된 itemId값을 비교해서 같은 값만 삭제되도록 하면 된다
handleQuantityChange함수를 수정해서 사용하면 된다
const handleQuantityChange = (quantity, itemId) => {
const newCartItems = [...cartItems]
let findIdx = cartItems.findIndex((item) => item.itemId === itemId)
newCartItems[findIdx].quantity = quantity
setCartItems(newCartItems)
}
findIndex를 사용해서 인덱스 번호를 찾아준 뒤
newCartItems[인덱스번호]의 수량 = 받아온 수량 으로 바꿔주면 된다
이렇게 하면 장바구니에서 위 아래 버튼을 클릭해서도 수량 변경이 가능하다
"cartItems": [
{
"itemId": 1,
"quantity": 1
},
{
"itemId": 5,
"quantity": 7
},
{
"itemId": 2,
"quantity": 3
}
]
import React, { useState } from 'react'
import CartItem from '../components/CartItem'
import OrderSummary from '../components/OrderSummary'
export default function ShoppingCart({ items, cartItems, setCartItems }) {
const [checkedItems, setCheckedItems] = useState(cartItems.map((el) => el.itemId))
const handleQuantityChange = (quantity, itemId) => {
const newCartItems = [...cartItems]
let findIdx = cartItems.findIndex((item) => item.itemId === itemId)
newCartItems[findIdx].quantity = quantity
setCartItems(newCartItems)
}
const handleDelete = (itemId) => {
setCheckedItems(checkedItems.filter((el) => el !== itemId))
setCartItems(cartItems.filter((el)=> el.itemId !== itemId))
}
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>
)
}
import React from 'react'
export default function CartItem({
item,
checkedItems,
handleCheckChange,
handleQuantityChange,
handleDelete,
quantity
}) {
return (
<li className="cart-item-body">
<input
type="checkbox"
className="cart-item-checkbox"
onChange={(e) => {
handleCheckChange(e.target.checked, item.id)
}}
checked={checkedItems.includes(item.id) ? true : false} >
</input>
<div className="cart-item-thumbnail">
<img src={item.img} alt={item.name} />
</div>
<div className="cart-item-info">
<div className="cart-item-title" data-testid={item.name}>{item.name}</div>
<div className="cart-item-price">{item.price} 원</div>
</div>
<input
type="number"
min={1}
className="cart-item-quantity"
value={quantity}
onChange={(e) => {
handleQuantityChange(Number(e.target.value), item.id)
}}>
</input>
<button className="cart-item-delete" onClick={() => { handleDelete(item.id) }}>삭제</button>
</li >
)
}
제일 쉬운 부분이다
app.js에서 nav바에 cartItems을 넘겨준뒤
<Nav cartItems={cartItems}/>
{cartItems.length}
로 수량을 바꿔주면 끝난다
import React from 'react';
import { Link } from 'react-router-dom';
function Nav({cartItems}) {
return (
<div id="nav-body">
<span id="title">
<img id="logo" src="../logo.png" alt="logo" />
<span id="name">CMarket</span>
</span>
<div id="menu">
<Link to="/">상품리스트</Link>
<Link to="/shoppingcart">
장바구니<span id="nav-item-counter">{cartItems.length}</span>
</Link>
</div>
</div>
);
}
export default Nav;
시작하기 전에...
애플리케이션에서 사용하는 주요 상태는 다음과 같습니다. 시작하기 전에 반드시 컴포넌트 구조와, 데이터 흐름을 먼저 그림으로 그려보세요. 일을 보다 단순하게 만들 수 있습니다.
다시 한번 컴포넌트 구조를 파악하고 시작하는게 중요하구나 깨달았던 하루
너무 어렵다^~^