728x90
📃 DetailTabInfo 코드 설명
import React, { useState } from 'react'
import css from './DetailTabInfo.module.css'
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.visible : ''}`}>
<h3>제목1</h3>
<p>내용이 들어갑니다.</p>
<a href="#">test1</a>
<p>내용이 들어갑니다.</p>
<p>내용이 들어갑니다.</p>
<p>내용이 들어갑니다.</p>
</div>
<div className={`${css.tabContent} ${activeTab === 1 ? css.visible : ''}`}>
<h3>제목1</h3>
<p>내용이 들어갑니다.</p>
<p>내용이 들어갑니다.</p>
<p>내용이 들어갑니다.</p>
<p>내용이 들어갑니다.</p>
<a href="#">test2</a>
</div>
<div className={`${css.tabContent} ${activeTab === 2 ? css.visible : ''}`}>
<h3>제목1</h3>
<p>내용이 들어갑니다.</p>
<p>내용이 들어갑니다.</p>
<p>내용이 들어갑니다.</p>
<p>내용이 들어갑니다.</p>
</div>
</>
)
}
export default DetailTabInfo
🔍 useState로 탭 상태 관리
const [activeTab, setActive] = useState(0)
- useState는 React에서 상태 관리할 수 있게 해주는 hook이다.
- 이 코드에서 현재 선택된 탭 번호를 저장한다.
- 초기값은 0이라 첫번째 탭에서 기본으로 선택되어 있다.
activeTab: 현재 선택된 탭 번호
setActive: 상태를 바꿀 수 있는 함수
🔍 탭 버튼 만들기
const tabTiles = ['메뉴1', '메뉴2', '메뉴3']
tabTiles 배열을 통해서 버튼 이름을 담고있다.
.map으로 순회한다.
{tabTiles.map((title, i) => (
<button
key={i}
className={activeTab === i ? css.active : ''}
onClick={() => setActive(i)}
>
{title}
</button>
))}
.map으로 순회를 할 때마다 <button>을 만든다.
activeTab은 i일때 css.active가 작동적응된다.
onclick을 통해서 activetab을 바꾼다.
📃 DeatilPage 코드 설명
import React, { useState, useEffect } from 'react'
import { useLoaderData } from 'react-router-dom'
import css from './DetailPage.module.css'
import { formmatCurrency } from '@/utils/features'
import DetailTabInfo from '@/organism/DetailTabInfo'
import SimilarProducts from '@/organism/SimilarProducts'
import Modal from '@/components/Modal'
const DetailPage = () => {
const { product, relatedProducts } = useLoaderData()
console.log('DetailPage:product', product)
console.log('DetailPage:relatedProducts', relatedProducts)
const [isLoading, setIsLoading] = useState(true)
const [isModalOpen, setIsModalOpen] = useState(false)
const [count, setCount] = useState(1)
useEffect(() => {
// 컴포넌트가 마운트된 직후에는 로딩 상태로 표시
setIsLoading(true)
// 데이터가 로드된 후 로딩 상태 해제
if (product && product.id) {
// 약간의 지연 효과를 줘서 로딩 화면을 확인할 수 있도록
const timer = setTimeout(() => {
setIsLoading(false)
}, 0)
return () => clearTimeout(timer)
}
}, [product])
if (isLoading) {
return (
<div className="d-flex justify-content-center align-items-center" style={{ height: '50vh' }}>
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
)
}
// 수량 조절 함수
const decrease = () => {
setCount(prev => (prev > 1 ? prev - 1 : 1))
}
const increase = () => {
setCount(prev => prev + 1)
}
// 장바구니 버튼 클릭 -> 모달 열기
const handleAddToCart = () => {
setIsModalOpen(true)
}
const closeModal = () => {
setIsModalOpen(false)
}
return (
<main>
<h2>DetailPage</h2>
<div className={css.detailCon}>
<div className={css.imgWrap}>
<img src={`/public/img/${product.img}`} alt={product.title} />
{product.discount > 0 && <p className={css.discount}>{product.discount} %</p>}
</div>
<div className={css.infoWrap}>
<p className={css.title}>{product.title}</p>
<p className={css.price}>{formmatCurrency(product.price)}</p>
<p className={css.category}>{product.category}</p>
<div className={css.btnWrap}>
<div className={css.counterArea}>
<button onClick={decrease}>-</button>
<span>{count}</span>
<button onClick={increase}>+</button>
</div>
<button className={css.addBtn} onClick={handleAddToCart}>
장바구니 담기
</button>
</div>
</div>
</div>
<DetailTabInfo />
<SimilarProducts relatedProducts={relatedProducts} />
{isModalOpen && <Modal product={product} count={count} onClose={closeModal} />}
</main>
)
}
export default DetailPage
🔍 useLoaderData
const { product, relatedProducts } = useLoaderData()
가까운 경로로 loader 또는 client loader에서 데이터를 변환한다.
페이지 라우팅될때 미리 데이터를 로드해서 받아오는 함수이다.
- 이 페이지 들어오기 전에 loader 함수에서 상품 데이터, 관련 상품 데이터 받아놓고 그걸 여기서 받아오는 것이다. 그러면 서버에서 가져온 상품 정보를 이 페이지에서 바로 사용할 수 있다.
🧪 과제
1. 장바구니 스타일링
p {
margin: 5px;
}
.btnCancel {
border: none;
width: 120px;
height: 30px;
border-radius: 20px;
margin-right: 5px;
}
.btnAdd {
border: none;
width: 120px;
height: 30px;
border-radius: 20px;
background-color: var(--dark-colors-accent-dark);
color: var(--dark-colors-white-dark);
}
이미지 사이즈를 키웠고,
가격, 할인, 수량 텍스트를 추가해줬다.
그리고 취소 버튼과 장바구니 담기의 버튼 스타일링을 해줬다.
구매 유도를 위해 취소의 백그라운드 컬러는 회색으로 하고
장바구니 담기는 이 사이트의 메인컬러로 줬다.
2. 수량, 삭제 기능 추가해보기
왼쪽은 starter페이지이고 오른쪽이 디자인 시안이다.
일단 사진, 상품명, 가격, 수량 카운트 삭제 기능이 필요하다.
큰솔창에
기능 구현이 안되길래.. 다음에 실습 보고 문제점을 파악해야겠다.
728x90
'💡 URECA > 🗒️ 스터디 노트' 카테고리의 다른 글
[URECA] Day58 React PROJ Shop URL (0) | 2025.04.22 |
---|---|
[URECA] Day57 React Shop PROJ - CartPage, ShopPage (0) | 2025.04.21 |
[URECA] Day55 React Shop PROJ 4편 디바운스 & 쓰로틀 정복기, 프록시 서버 설정 (0) | 2025.04.17 |
[URECA] Day54 리액트 lazy, suspense, axios (1) | 2025.04.15 |
[URECA] Day53 React useEffect(), useLocation() (0) | 2025.04.14 |