next에서 무한스크롤 처리 (IntersectionObserver 처리)
넥스트에서는 infinite scroll 처리를 어떻게 할까?
여러가지 방법이 있겠지만 내가 사용하는 것은 화면에 마지막 요소가 나왔을 때 observer로 관찰한 후 처리하는 방식이다. ssr을 유지하면서 처리해 보자.
무한스크롤은 정말 귀찮지만 너무 많이 쓰인다…ㅠㅠ
import { Container, Divider } from "@mui/material";
import Link from "next/link";
import { useEffect, useState, useRef, useCallback } from "react";
import Header from "../../components/Header";
import API from "../../lib/api";
import { useRouter } from "next/router";
const BookPublicList = ({ ssrData, pageNumber }) => {
const [hasMore, setHasMore] = useState(false);
const observer = useRef();
const router = useRouter();
useEffect(() => {
if (ssrData) {
if (ssrData.error) {
// 에러표시
console.log(ssrData.error);
} else {
// 마지막 페이지 비교해서 더 불러올게 있는지 처리
if (parseInt(pageNumber, 10) < parseInt(ssrData.lastPage, 10)) {
setHasMore(true);
} else {
setHasMore(false);
}
}
}
}, [ssrData]);
// 페이지네이션 처리
const handlePagination = (page) => {
const path = router.pathname;
const query = router.query;
query.page = parseInt(page, 10) + 1;
router.push(
{
pathname: path,
query: query,
},
undefined,
{ scroll: false }
);
};
// 마지막 요소 화면에 등장하면 페이지네이션 처리 발동
const lastElementRef = useCallback(
(node) => {
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
handlePagination(pageNumber);
}
});
if (node) observer.current.observe(node);
},
[hasMore, handlePagination]
);
return (
<div>
<Header />
<Container maxWidth="md">
<div>
<h1>책 리스트</h1>
</div>
<ul>
{ssrData.books &&
ssrData.books.map((book, index) => (
<div
key={book._id}
className="mb-10"
ref={ssrData.books.length === index + 1 ? lastElementRef : null}
>
<li className="mb-10">
<Link href={`/books/${book._id}`}>
<a>{book.title}</a>
</Link>
</li>
<Divider />
</div>
))}
</ul>
</Container>
</div>
);
};
export const getServerSideProps = async ({ query }) => {
const page = query.page || 1;
let ssrData = null;
try {
const res = await API.get(
`${process.env.NEXT_PUBLIC_SERVER}/api/books?page=${page}`
);
if (res.status !== 200) {
throw new Error("Failed to fetch");
}
ssrData = await res.data.data;
} catch (err) {
ssrData = { error: { message: err.message } };
}
return { props: { ssrData, pageNumber: page } };
};
export default BookPublicList;
이렇게 간단하게 처리를 해보았다.
백엔드는 기존 pagination이랑 조금 다른데 skip이 없고 limit로 페이지 * n개의 데이터를 반납한다.
router.get("/", async (req, res, next) => {
try {
const page = req.query.page ? req.query.page : 1;
const perPage = 20;
const paging = parseInt(page, 10) * perPage;
const totalPageCount = await Book.find({ publish: true }).countDocuments();
const lastPage = Math.ceil(totalPageCount / perPage);
let books = await Book.find({ publish: true })
.limit(paging)
.sort({ updatedAt: -1 });
if (!books.length) {
return res.status(404).json({
error: "북을 찾을 수 없습니다.",
});
}
res.status(200).json({ data: { books, lastPage } });
} catch (e) {
next(e);
}
});
그럼 즐코딩~!
공유하기
조회수 : 827