[Day57] 쇼핑몰 실습 - Tab, Modal, Cart
[Day57] 쇼핑몰 실습 - Tab, Modal, Cart
🔸 tab
구현 포인트
1️⃣ useState(0)
으로 현재 선택된 탭의 index
저장
1
const [activeTab, setActive] = useState(0)
2️⃣ 탭 버튼 클릭 시 해당 index
를 상태로 변경
1
2
3
4
5
6
7
8
9
{tabTiles.map((title, i) => (
<button
key={i}
className={activeTab === i ? css.active : ''}
onClick={() => setActive(i)}
>
{title}
</button>
))}
3️⃣ 각 탭 콘텐츠 영역에서 activeTab === i
조건으로 보여줄 콘텐츠를 결정
1
<div className={`${css.tabContent} ${activeTab === 0 ? css.active : ''}`}>
전체 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const DetailTabInfo = () => {
const [activeTab, setActive] = useState(0)
const tabTiles = ['메뉴1', '메뉴2', '메뉴3']
return (
<>
<div className={css.tabBtn}>
{tabTiles.map((title, i) => (
<button
key={i}
className={activeTab === i ? css.active : ''}
onClick={() => setActive(i)}
>
{title}
</button>
))}
</div>
<div className={`${css.tabContent} ${activeTab === 0 ? css.active : ''}`}>
<h3>제목</h3>
</div>
<div className={`${css.tabContent} ${activeTab === 1 ? css.active : ''}`}>
<h3>제목2</h3>
</div>
<div className={`${css.tabContent} ${activeTab === 2 ? css.active : ''}`}>
<h3>제목3</h3>
</div>
</>
)
}
export default DetailTabInfo
UI
🔸 Modal
구현 포인트
1️⃣ useEffect
를 사용해서 모달이 켜질 때(처음 마운트 시) active
클래스 부여
1
2
3
4
5
6
7
8
9
10
11
useEffect(() => {
const timer = setTimeout(() => {
setIsActive(true)
document.body.style.overflow = 'hidden'
}, 5)
return () => {
clearTimeout(timer)
document.body.style.overflow = 'auto'
}
}, [])
✅ 트랜지션 효과 제대로 동작하게 하기 위해 setTimeout 사용
setTimeout
을 안쓰면 컴포넌트가 렌더링되자마자 바로 active 클래스가 붙어버림- 문제는 애니메이션(transition)이 안 먹는 경우가 생김
- 그래서 브라우저가 먼저 초기 스타일을 렌더링하게 한 후
active
를 붙임
2️⃣ 모달이 열리면 body.style.overflow = 'hidden'
으로 스크롤 방지
1
document.body.style.overflow = 'hidden'
3️⃣ 취소 버튼이나 외부 버튼 클릭 시 닫힘 (클래스 제거 후 setTimeout
으로 애니메이션 적용)
1
2
3
4
const handleClose = () => {
setIsActive(false)
setTimeout(onClose, 300)
}
✅ 그냥 바로 onClose() 호출하면 모달이 애니메이션 없이 툭 꺼짐
4️⃣ “장바구니 담기” 누르면 addToCart
API 실행 → 모달 닫고 → /cart
로 이동
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const handleAddToCart = async () => {
const cartItem = {
id: product.id,
title: product.title,
img: product.img,
price: product.price,
category: product.category,
discount: product.discount,
count: count,
}
await addToCart(cartItem)
handleClose()
navigate('/cart')
}
전체 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const Modal = ({ product, count, onClose }) => {
const [isActive, setIsActive] = useState(false)
const navigate = useNavigate()
useEffect(() => {
const timer = setTimeout(() => {
setIsActive(true)
document.body.style.overflow = 'hidden'
}, 5)
return () => {
clearTimeout(timer)
document.body.style.overflow = 'auto'
}
}, [])
const handleClose = () => {
setIsActive(false)
setTimeout(onClose, 300)
}
const handleAddToCart = async () => {
try {
const cartItem = {
id: product.id,
title: product.title,
img: product.img,
price: product.price,
category: product.category,
discount: product.discount,
count: count,
}
await addToCart(cartItem)
handleClose()
navigate('/cart')
} catch (err) {
console.log('err', err)
}
}
return (
<div className={`${css.modal} ${isActive ? css.active : ''}`}>
<div className={`${css.container} `}>
<div className={css._inner}>
<h2>장바구니</h2>
<div className={css.imgWrap}>
<img src={`/public/img/${product.img}`} alt={product.title} />
</div>
<div className={css.info}>
<p>{product.tilte}</p>
<p>{product.price.toLocaleString()}원</p>
{product.discount > 0 && <p>{product.discount}%</p>}
<p>{count}</p>
<p>총 가격 : {(product.price * count).toLocaleString()}</p>
</div>
<button onClick={handleClose}>취소</button>
<button onClick={handleAddToCart}>장바구니 담기</button>
</div>
<button className={css.btnClose} onClick={handleClose}>
<i className="bi bi-x-lg"></i>
</button>
</div>
</div>
)
}
UI 완성 전 ! 일단 데이터 보내는거 확인
🔸 Cart
구현 포인트
1️⃣ 라우터 설정 으로 /cart
진입 시, CartPage
렌더링
1
{ path: '/cart', element: <CartPage />, loader: cartPageLoader }
2️⃣ loader
함수로 장바구니 데이터 가져오기
1
2
3
4
5
6
7
8
9
10
11
export const cartPageLoader = async () => {
try {
const cartItems = await getCartData()
if (!cartItems || cartItems.length === 0) {
return { cartItems: [] }
}
return cartItems
} catch (err) {
console.log('err', err)
}
}
3️⃣ 장바구니 데이터 처리 cartApi.js
getCartData()
: 장바구니 전체 조회
1
2
3
4
5
6
7
8
9
export const getCartData = async () => {
try {
const res = await axios.get(`/api/cart/`)
console.log('getCartData.js : getCartData', res)
return res.data
} catch (err) {
console.log('err', err)
}
}
addToCart()
: 동일 상품 있으면 count 증가(PUT), 없으면 새로 추가(POST)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export const addToCart = async cartItem => {
try {
const cart = await getCartData()
const existingItem = cart.find(item => item.id === cartItem.id)
if (existingItem) {
const uedateItem = {
...existingItem,
count: existingItem.count + cartItem.count,
}
const res = await axios.put(`/api/cart/${existingItem.id}`, uedateItem)
return res.data
} else {
const res = await axios.post(`/api/cart/`, cartItem)
return res.data
}
} catch (err) {
console.log('err', err)
}
}
4️⃣ CartPage
에서 useLoaderData()
로 불러온 장바구니 데이터 확인
1
const cartList = useLoaderData()
UI 완성 전 ! 일단 데이터 받아오는거 확인
END
This post is licensed under CC BY 4.0 by the author.