<캐럿마켓 클론코딩> 강의에서 니꼬쌤이 pagination 코드 챌린지를 권유했다. SWR
은 take
와 skip
을 이용해 pagination
구현 어렵지 않았다.
GET
요청을 받으면 query
에 담긴 page
와 limit
을 받아온다. string
으로 오기 때문에 +
를 붙여 number
로 형변환한다.
pagination
로직은 (현재 page - 1) * limit
을 skip
에 할당하면 된다. take
는 가져올 데이터의 양, skip
은 건너뛸 데이터의 양을 의미한다. 예를 들어, take: 10
, skip: 10
이라면 0~9번 데이터를 패스하고 다음 10개, 즉 10~19번 데이터를 가져오는 셈이다.
// user.ts
if (req.method === "GET") {
const {
query: { page, limit },
} = req;
const products = await client.products.findMany({
include: {
user: {
select: {
id: true,
},
},
},
take: +limit,
skip: (+page - 1) * +limit,
orderBy: { created: "desc" },
});
res.json({
ok: true,
products,
});
}
useSWR
로 page
와 limit
을 파라미터로 담아 요청을 보내고, 받아온 데이터로 화면을 구성한다. 버튼의 onClick
으로 이전 페이지라면 page - 1
, 다음 페이지라면 page + 1
한다.
const Products = () => {
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(10);
const { data } = useSWR(
`/api/products?page=${page}&limit=${limit}`
);
const onPrevBtn = () => {
setPage((prev) => prev - 1);
};
const onNextBtn = () => {
setPage((prev) => prev + 1);
};
return (
<div>
{data?.products?.map((product) => (
<div key={product.id}>
<span>{product.name}</span>
<span>{product.price}</span>
</div>
))}
</div>
<button onClick={onPrevBtn}>이전</button>
<button onClick={onNextBtn}>다음</button>
);
};
export default Products;
SWR
에서는 무한 스크롤을 위한 useSWRInfinite
함수도 제공한다. Backend부분은 같다.
const getKey = (pageIndex, previousPageData) => {
if (pageIndex === 0) return `/api/products?&page=1&limit=10`;
if (pageIndex + 1 > +previousPageData.pages) return null;
return `/api/products?&page=${pageIndex + 1}&limit=10`;
};
const fetcher = (url) => fetch(url).then((res) => res.json());
export default function ProductList() {
const { data, setSize, size } = useSWRInfinite(getKey, fetcher);
const products = data ? data.map((item) => item.products).flat() : [];
const onNextBtn = () => {
setSize((prev) => prev + 1);
};
return data ? (
<>
<div>
{products?.map((product) => (
<div key={product.id}>
<span>{product.name}</span>
<span>{product.price}</span>
</div>
))}
</div>
<button onClick={onNextBtn}>더 보기</button>
</>
) : null;
}
getKey
함수를 설정해야 하는데, 두 가지 인자(pageIndex, previousPageData
)만 받아서 limit
은 url에 직접 적어뒀다. 유저가 수정할 이유는 없을 테니, Backend로 빼도 괜찮겠다.
getKey
로 인해 페이지가 변동될 때마다 데이터를 추가로 로드하는데, useSWR
은 페이지를 새로고침해 렌더링하는 반면, useSWRInfinite
는 새로고침 없이 추가로 데이터를 렌더링한다. 따라서 더 보기
버튼을 누르면 현재의 마지막 데이터 밑에 새로운 데이터가 추가된다. 버튼 대신 scroll
이벤트를 사용할 수도 있다.
const [page, setPage] = useState(1);
function handleScroll() {
if (
document.documentElement.scrollTop + window.innerHeight ===
document.documentElement.scrollHeight
) {
setPage((p) => p + 1);
}
}
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);