import { ProductType } from '@/shared/types/data.type';
import { useState, useEffect } from 'react';
const useMoreView = (initialPage = 1, itemsPerPage = 10) => {
// ✅ 상태) 페이지 수, 아이템, 더보기가 가능한지 여부
const [page, setPage] = useState<number>(initialPage);
const [items, setItems] = useState<ProductType[]>([]);
const [hasMore, setHasMore] = useState<boolean>(true);
// ✅ 아이템을 불러오기 위해 API 요청을 보내는 함수
const getItems = async (page: number) => {
const response = await fetch(
`/api/products?page=${page}&limit=${itemsPerPage}`
);
const data = await response.json();
return data;
};
// ✅ 불러온 아이템을 추가하고, 페이지 상태와 더보기 여부 상태를 변경하는 함수
const loadMore = async () => {
const newItems = await getItems(page);
setItems((prevItems: ProductType[]) => [...prevItems, ...newItems]);
setPage(prevPage => prevPage + 1);
if (newItems.length < itemsPerPage) {
setHasMore(false);
}
};
// ✅ 렌더링 되고 난 이후 마운트될 때 초기 데이터 불러오기
useEffect(() => {
loadMore();
}, []);
return { items, loadMore, hasMore };
};
export default useMoreView;
상품 목록 페이지에서 useMoreView 사용하기
import ProductItem from '@/components/product/ProductItem';
import { Button } from '@/components/ui/button';
import useMoreView from '@/features/products/useMoreView';
import { ProductType } from '@/shared/types/data.type';
const ProductList = () => {
const { items, loadMore, hasMore } = useMoreView();
return (
<div>
<ul className="grid grid-cols-2 gap-x-2 gap-y-4">
{items.map((product: ProductType) => (
<ProductItem
key={product.id}
product={product}
/>
))}
</ul>
{/*✅ 더보기 여부(hasMore)에 따른 더보기 버튼 렌더링 */}
{hasMore && (
<div className="mt-4 flex justify-center">
<Button onClick={loadMore}>더보기</Button>
</div>
)}
</div>
);
};
export default ProductList;
import { fetcher, QueryKeys } from '@/queryClient';
import { useQuery } from '@tanstack/react-query';
import { useParams } from 'react-router-dom';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
const ProductDetail = () => {
const { id } = useParams();
const [showDetails, setShowDetails] = useState(false);
const { data } = useQuery({
queryKey: [...QueryKeys.PRODUCTS, id],
queryFn: () => fetcher({ method: 'GET', path: `/products/${id}` })
});
if (!data) return null;
const { category, description, image, price, rating, title } = data;
return (
<div className="p-4">
<img
src={image}
alt={title}
className="h-[500px] w-full rounded-md object-cover"
/>
<h2 className="mt-2 text-xl font-bold">{title}</h2>
<p className="text-lg font-semibold">${price}</p>
<p className="text-gray-700">{category}</p>
<p className="text-sm text-gray-600">
Rating: {rating.rate} ({rating.count})
</p>
<div
className={`relative bg-slate-300 ${showDetails ? 'h-[600px]' : 'max-h-20 overflow-hidden'}`}>
<h3 className="mt-4 text-xl font-bold">상세정보</h3>
<p className="text-gray-700">{description}</p>
{!showDetails && (
<div className="absolute bottom-0 left-0 h-8 w-full bg-gradient-to-t from-white to-transparent"></div>
)}
</div>
<Button
onClick={() => setShowDetails(!showDetails)}
className="mt-4 w-full rounded border border-solid border-black bg-white p-2 text-black hover:bg-black/5">
{showDetails ? '상세정보 접기' : '상세정보 보기'}
</Button>
</div>
);
};
export default ProductDetail;