[SWR] Pagination

찐새·2022년 6월 3일
2

next.js

목록 보기
18/41
post-thumbnail

SWR Pagination

<캐럿마켓 클론코딩> 강의에서 니꼬쌤이 pagination 코드 챌린지를 권유했다. SWRtakeskip을 이용해 pagination 구현 어렵지 않았다.

previous, next Pagination

Backend

GET 요청을 받으면 query에 담긴 pagelimit을 받아온다. string으로 오기 때문에 +를 붙여 number로 형변환한다.

pagination 로직은 (현재 page - 1) * limitskip에 할당하면 된다. 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,
    });
  }

Frontend

useSWRpagelimit을 파라미터로 담아 요청을 보내고, 받아온 데이터로 화면을 구성한다. 버튼의 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;

Infinite Scroll

SWR에서는 무한 스크롤을 위한 useSWRInfinite함수도 제공한다. Backend부분은 같다.

Frontend

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);
    };
  }, []);

참고
노마드 코더 - 캐럿마켓 클론코딩
SWR - Pagination

profile
프론트엔드 개발자가 되고 싶다

0개의 댓글