[Day59]쇼핑몰 실습 - ShopPage,Pagination
[Day59]쇼핑몰 실습 - ShopPage,Pagination
URL과 params에 대한 핵심 내용은 따로 포스팅 해두었다 !
URL? URLSearchParams? useSearchParams?
Shop
기능
- 상품 목록 페이지
- 카테고리 필터링
- 정렬 기능
- 페이지네이션
- URL Query 파라미터를 활용한 상태 관리
?category=new&_sort=price&_page=2
Router 설정 router.jsx
1
{ path: '/shop', element: <ShopPage />, loader: shopPageLoader }
- /shop 경로로 진입 시:
- shopPageLoader로 데이터를 미리 불러오고
- ShopPage 컴포넌트가 해당 데이터를 받아 렌더링
shopPageLoader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const shopPageLoader = async ({ request }) => {
const url = new URL(request.url)
const page = url.searchParams.get('_page') || 1
const per_page = url.searchParams.get('_per_page') || 12
const category = url.searchParams.get('category') || ''
const sort = url.searchParams.get('_sort') || ''
let queryString = `_page=${page}&_per_page=${per_page}`
category ? (queryString += `&category=${category}`) : queryString
sort ? (queryString += `&_sort=${sort}`) : queryString
try {
const products = await getProductsData(queryString)
return { products, per_page }
} catch (err) {
console.log('err----', err)
throw new Response('상품 데이터를 가져오는 중 오류 발생', {
status: err.status || 500,
})
}
}
request.url
에서query
파라미터 추출- 해당 파라미터로 API 호출용 queryString 생성
getProductsData(queryString)
로 상품 데이터 요청- 반환값은
{ products, per_page }
형태로 전달됨
❓ loader({ request })의 request는 어디서 오는가?
✅ React Router가 자동으로 넣어준다 !
request는 브라우저가 현재 라우트를 요청할 때의 정보를 담은 Fetch API 스타일의 Request 객체
1 2 3 4 5 // 이 정보들이 포함되어 있음 request.url // 현재 페이지의 전체 URL (query 포함) request.method // 'GET' 같은 HTTP 메서드 request.headers // 헤더 정보 request.signal // AbortController용
ShopPage
🔹 카테고리 필터링
1
2
3
4
5
6
7
const handleCategoryFilter = category => {
const params = new URLSearchParams(searchParams) // 현재 파라미터 정보 유지
params.set('_page', 1) // 페이지를 1로 초기화
params.set('_per_page', per_page) // 페이지당 상품 수를 설정
category ? params.set('category', category) : params.delete('category') // 카테고리 필터링
navigate(`/shop/?${params}`) // URL 변경
}
1
2
3
4
5
6
<button
onClick={() => handleCategoryFilter('new')}
className={currentCategory === 'new' ? css.active : ''}
>
신상품(new)
</button>
- 클릭 시 URL 파라미터 변경 → loader 재실행 → 상품 재조회
🔹 정렬 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const handleSort = sortOption => {
const params = new URLSearchParams(searchParams)
params.set('_page', 1)
params.set('_sort', sortOption) // 선택한 정렬 기준을 _sort로 URL에 반영
setIsDown(false)
navigate(`/shop/?${params}`)
}
const sortTextMap = {
id: '등록순',
price: '낮은 가격순',
'-price': '높은 가격순',
discount: '낮은 할인순',
'-discount': '높은 할인순',
}
const getSortText = () => {
return sortTextMap[sortCase] || '등록순'
}
1
2
3
4
5
6
<div className={css.sortHeader} onClick={() => setIsDown(!isDown)}>
<p>{getSortText()}</p>
</div>
<ul>
<li onClick={() => handleSort('price')}>낮은 가격순</li>
</ul>
- 토글로 정렬 옵션 노출
- 정렬 선택 시 navigate()로 URL 변경 → loader 재호출
Pagination
주요 흐름
1
2
3
const Pagination = ({ initProductsData }) => {
const [searchParams] = useSearchParams()
const navigate = useNavigate()
initProductsData
:loader
에서 받아온 상품 데이터useSearchParams
: 현재 URL 쿼리 파라미터 정보 가져옴navigate
: 페이지 변경 시 URL 변경용 함수
🔹 페이지 이동 : handlePageChange
1
2
3
4
5
const handlePageChange = page => {
const params = new URLSearchParams(searchParams)
params.set('_page', page)
navigate(`/shop/?${params}`)
}
_page
파라미터를 원하는 페이지로 변경한 후 URL 업데이트 →loader
재호출됨
🔹 페이지 번호 배열 생성: getPageNumbers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const getPageNumbers = () => {
// 한번에 보여줄 최대 페이지 번호 수
const maxPageNumbers = 10
// 전체 페이지가가 최대 페이지보다 작으면 모든 페이지 번호 표시
if (pages <= maxPageNumbers) {
return Array.from({ length: maxPageNumbers }, (_, i) => i + 1)
}
// 페이지가 많을 경우 현재 페이지 번호를 기준으로 주변 번호 생성
// 예) 현재 페이지 15 => 10 ~ 25까지 보여줌
let startPage = Math.max(1, currentPage - Math.floor(maxPageNumbers / 2))
let endPage = Math.min(pages, startPage + maxPageNumbers - 1)
startPage = Math.max(1, endPage - maxPageNumbers + 1)
return Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i)
}
1
2
3
4
5
<button onClick={() => handlePageChange(first)}>처음으로 이동</button>
<button onClick={() => handlePageChange(prev)}><</button>
{pageNumbers.map(...)} // 페이지 번호 버튼
<button onClick={() => handlePageChange(next)}>{'>'}</button>
<button onClick={() => handlePageChange(last)}>마지막 페이지</button>
first
,prev
,next
,last
는 모두initProductsData.products
에서 전달받은 값- 현재 페이지일 경우 비활성화(disabled), 또는 스타일(className) 처리
상품 페이지 + 페이지네이션
END
This post is licensed under CC BY 4.0 by the author.