[GraphQL] Pagination , InfiniteScroll

DongEun·2022년 11월 21일
2
post-thumbnail

TMI : 벨로그도 GraphQL로 작성되어있어서 소스를 까봤는데 역시 외부여서 자세하게는 안되는구나,,


Pagination

pagination이란 보여줄 데이터가 많으나 한번에 처리 할 수 없어 페이지 번호를 클릭해서 이동하는 방식의 페이지 처리 방법이에요

GraphQL에서는 refetch 라는 함수로 구현이 가능해요

export default function PaginationPage() {
  const [startPage, setStartPage] = useState(1); // 초기값 설정 (index번호는 0부터 시작이기때문에)

  const { data, refetch } = useQuery<
    Pick<IQuery, "fetchBoards">,
    IQueryFetchBoardsArgs
   >(FETCH_BOARDS);

   const onClickPage = (event: MouseEvent<HTMLSpanElement>) => {
    // refetch(page : 보여줄 페이지번호)
	void refetch({ page: Number(event.currentTarget.id) });
   };

   return (
    <div>
     {data?.fetchBoards?.map((el) => ( 
     // data?. => 데이터가 있으면 / fetchBoards?. => fetchBoards가 있으면 map메소드 실행
      <div key={el._id}>
       <span >{el.title}</span>
       <span >{el.contents}</span>
      </div>
     ))}
     <PageNationWrap>
      {new Array(10).fill(0).map(
      // 초기에 반복할 갯수가 없기에 new Array로 반복할 횟수를 설정
      // new Array(10)만하면 빈값이기에 fill(0)으로 모든 배열에 값 넣어주기(빈값이면 반복문이 돌지 않아요)
       (_, index) => (
       // _는 사용하지 않는 매개변수일경우 작성
         <PageButton
          key={index + startPage}
          id={String(index + startPage)}
          onClick={onClickPage}
         >
          {index + startPage}
         </PageButton>
        )
      )}
	  </PageNationWrap>
    </div>
   );
 }

여기까지가 pagination 기초라 생각해요
그렇지만 저는 이전버튼과 다음버튼 구현 및 데이터가 없으면 번호를 노출하면 안되기에 조금 더 설정할 예정이에요.

const [isPage, setIsPage] = useState(1); // 현재 보고 있는 페이지


 const { data: dataBoardsCount } = useQuery<
  Pick<IQuery, "fetchBoardsCount">,
  IQueryFetchBoardsCountArgs
 >(FETCH_BOARDS_COUNT);
// boards의 총 갯수
 
 const lastPage =
  dataBoardsCount != null
   ? Math.ceil(dataBoardsCount?.fetchBoardsCount / 10)
   : 1;
// 마지막 페이지 구하기
// 만약에 796개의 리스트가 있다면 반올림하여 80개의 페이지가 마지막 페이지


 const onClickPrevPage = () => { 			// 이전페이지
  if (startPage === 1) return; 				// 첫번째 페이지면 작동금지 (early return 패턴)
  setStartPage(startPage - 10); 			// 10씩 줄이기 (페이지네이션이 10개를 노출 하기에)
  void refetch({ page: startPage - 10 });	
  setIsPage(startPage - 10); 				
 };

 const onClickNextPage = () => {			// 이전페이지
  if (startPage + 10 <= lastPage) {			// 라스트 페이지를 넘기기전까지만 작동 => (71 + 10 <= 80) false
   setStartPage(startPage + 10);
   void refetch({ page: startPage + 10 });
   setIsPage(startPage + 10);
  }
 };

위에 코드를 추가하여 이전버튼과 다음버튼 그러고 버튼의 active까지 구현을 했어요

최종코드

export default function StaticRoutedPage() {
 const [startPage, setStartPage] = useState(1);
 const [isPage, setIsPage] = useState(1);

 const { data, refetch } = useQuery<
  Pick<IQuery, "fetchBoards">,
  IQueryFetchBoardsArgs
 >(FETCH_BOARDS);

 const { data: dataBoardsCount } = useQuery<
  Pick<IQuery, "fetchBoardsCount">,
  IQueryFetchBoardsCountArgs
 >(FETCH_BOARDS_COUNT);

 const lastPage =
  dataBoardsCount != null
   ? Math.ceil(dataBoardsCount?.fetchBoardsCount / 10)
   : 1;

 const onClickPage = (event: MouseEvent<HTMLSpanElement>) => {
  void refetch({ page: Number(event.currentTarget.id) });
  setIsPage(Number(event.currentTarget.id));
 };

 const onClickPrevPage = () => {
  if (startPage === 1) return;
  setStartPage(startPage - 10);
  void refetch({ page: startPage - 10 });
  setIsPage(startPage - 10);
 };

 const onClickNextPage = () => {
  if (startPage + 10 <= lastPage) {
   setStartPage(startPage + 10);
   void refetch({ page: startPage + 10 });
   setIsPage(startPage + 10);
  }
 };

 return (
  <div>
   {data?.fetchBoards?.map((el) => (
    <div key={el._id}>
     <span style={{ margin: "10px" }}>{el.title}</span>
     <span style={{ margin: "10px" }}>{el.contents}</span>
    </div>
   ))}
   <PageNationWrap>
    <PageButton disabled={startPage === 1} onClick={onClickPrevPage}>
     <LeftOutlined />
    </PageButton>
    {new Array(10)
     .fill(0)
     .filter((_, filterIndex) => filterIndex + startPage <= lastPage)
     .map((_, mapIndex) => (
      <PageButton
       key={mapIndex + startPage}
       id={String(mapIndex + startPage)}
       onClick={onClickPage}
       isPageActive={isPage === mapIndex + startPage ? true : false}
      >
       {mapIndex + startPage}
      </PageButton>
     ))}
    <PageButton disabled={startPage + 10 > lastPage} onClick={onClickNextPage}>
     <RightOutlined />
    </PageButton>
   </PageNationWrap>
  </div>
 );
}



Infinite Scroll

유튜브 또는 페이스북과 같이, 페이지를 아래로 스크롤 하다가 종단점에 도달하면

새로운 데이터가 계속해서 추가되는 방식의 페이지 처리 방법을 무한스크롤 방식이라고 합니다.

react에서 인기있는 무한스크롤 라이브러리가 2개가 있는데 그중에 react infinite scroller 을 사용할 예정이에요
react infinite scroller

npm 사용자
npm install react-infinite-scroller --save

yarn 사용자
yarn add react-infinite-scroller

타입스크립트 사용하시는경우 추가로 설치 해주세요

npm 사용자
npm install --save-dev @types/react-infinite-scroller

yarn 사용자
yarn add --dev @types/react-infinite-scroller

개인적으로 무한스크롤은 페이지네이션보다 구현하기에 더 쉬운거같고 좀 더 사용하기 편할거같다는 생각이에요
그치만 단점으로는 페이지네이션처럼 원하는 페이지만 볼 수 없다는게 단점인거같아요 (근데 검색하면 그만 아닌가..)

무한 스크롤을 적용하고 싶은 영역을 InfiniteScroll 태그로 감싸서 사용해요

코드로 마저 설명을 드린다면

import InfiniteScroll from "react-infinite-scroller"; // 추가 해주세요

export default function InfiniteScrollPage() {
 const { data, fetchMore } = useQuery<
  Pick<IQuery, "fetchBoards">,
  IQueryFetchBoardsArgs
 >(FETCH_BOARDS);
 // fetchMore를 추가 해주세요

 const onFectchMore = () => {
  if (data === undefined) return; // data가 없을경우 종료 (early return 패턴)

  void fetchMore({
   variables: {
    page: Math.ceil(data.fetchBoards.length / 10) + 1, 
	// fetchBoards의 데이터는 계속 쌓임 그러면 10개씩 계속 증가
   },
   updateQuery(prev, { fetchMoreResult }) { // prev : 이전데이터 , fetchMoreResult : 쿼리 호출 후 결과
    if (fetchMoreResult.fetchBoards === undefined) {
     return {
      fetchBoards: [...prev.fetchBoards],
     };
    } // 쿼리 호출후 결과가 undefined일 경우 이전값까지만 노출 (early return 패턴)
    return {
     fetchBoards: [...prev.fetchBoards, ...fetchMoreResult.fetchBoards],
    }; // 이전 데이터와 호출후 데이터 합치기
   },
  });
 };

 return (
  <div>
   <InfiniteScroll
    pageStart={0} 
    loadMore={onFectchMore} // 화면이 최하단으로 갔을경우 실행할 함수
    hasMore={true}
   >
    {data?.fetchBoards?.map((el) => (
     <div key={el._id}>
      <span style={{ margin: "10px" }}>{el.title}</span>
      <span style={{ margin: "10px" }}>{el.contents}</span>
     </div>
    ))}
   </InfiniteScroll>
  </div>
 );
}



기능 의주다보니 코드로만 작성을 하였는데,, 정리하기 너무 힘들다,,

profile
다채로운 프론트엔드 개발자

0개의 댓글