검색
검색
회원가입로그인

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);
  }
});

그럼 즐코딩~!

조회수 : 516
공유하기
카카오로 공유하기
페이스북 공유하기
트위터로 공유하기
url 복사하기