react

react-query를 이용해 pagenation을 구현해보자

2023. 6. 29. 19:41
목차
  1. pagenation이란?
  2. 필요한 기능 정의하기
  3. 환경 설정
  4. 마치며

pagenation이란?

페이지네이션은 일정한 갯수로 아이템을 보여주는 것을 의미할 수 있습니다.

페이지네이션을 위한 간단한 알고리즘이 존재하니 기억해두시면 좋습니다.

 

그렇게 어렵지는 않은데 버튼을 일정하게 유지시켜주기 위해서 필요한 알고리즘입니다.

예컨대 1부터 10까지의 페이지를 보여주는 위 그림과 같은 페이지버튼이 있다고 가정해보면

1 2 3 4 5 6 7 8 9 10 이 나열되어야 하고

11페이지로 넘어가게되면

11 12 13 14 15 16 17 18 19 20이 나열되어야 할 것입니다.

이를 위해서 사용되어야하는 알고리즘입니다.

  const [listSize, setListSize] = React.useState(20);
  const [activePage, setActivePage] = React.useState(1);
  const [buttonCount, setButtonCount] = React.useState(10);
  let firstNum = activePage - (activePage % listSize) + 1;
  let lastNum = activePage - (activePage % listSize) + buttonCount;

React로 구현한다고하면 이와 같은 형태로 구현할 수 있을 것입니다.

listSize는 한번에 표시할 컨텐츠의 갯수를 의미합니다.

activePage는 현재 보고있는 페이지를 의미합니다.

buttonCount는 한번에 보여줄 버튼의 갯수를 의미합니다.

firstNum과 listNum은 버튼의 시작점과 끝점을 의미합니다.

 

하지만 이번에는 먼저 간단히 prev/next 버튼만 구현을 하고 넘어가보도록하겠습니다.


필요한 기능 정의하기

모두가 숫자 버튼이 있는 페이지네이션을 구현할 필요는 없습니다.

우선 기초적인 기능을 수행하는 페이지네이션을 만들어 볼 것입니다.

1. 20개 단위로 아이템을 불러올 수 있어야한다.

2. prev 버튼, next버튼을 누르면 페이지의 앞 / 뒤로 이동할 수 있어야 한다.

3. prefetch를 통해 다음에 볼 데이터를 미리 받아올 수 있어야한다.

 

이 세가지 조건을 만족하는 페이지네이션 예제를 만들어봅시다.

아주 운이 좋게도 리액트쿼리는 저 모든 요구사항을 만족시킬만큼 기능을 제공해줍니다.

https://tanstack.com/query/v4/docs/react/guides/paginated-queries#better-paginated-queries-with-keeppreviousdata

 

Paginated / Lagged Queries | TanStack Query Docs

Rendering paginated data is a very common UI pattern and in TanStack Query, it "just works" by including the page information in the query key:

tanstack.com

공식문서의 예제를 참고하며 작성했습니다.

최대한 적은 파일로 예제를 확인할 수 있도록 하기 위해

모듈화를 지양하면서 작성했습니다.

사용하는 api는 pokemon api입니다.


환경 설정

vite / typescript로 구축한 프로젝트입니다.

npx create-vite@latest

https://github.com/XionWCFM/react-query-infinite

 

GitHub - XionWCFM/react-query-infinite

Contribute to XionWCFM/react-query-infinite development by creating an account on GitHub.

github.com

자세한 코드는 위 레포에서 확인할 수 있습니다.

 

npm i @tanstack/react-query @tanstack/react-query-devtools

아래 실습에서 사용할 react-query 버전은 v4.29.14입니다.

App.tsx

function App() {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        suspense: true,
      },
    },
  });

  return (
    <QueryClientProvider client={queryClient}>
      <main>
        <React.Suspense fallback={<Loading />}>
          <PagenationComponent />
        </React.Suspense>
      </main>
      <ReactQueryDevtools initialIsOpen={true} />
    </QueryClientProvider>
  );
}

export default App;

리액트 쿼리 사용을 위해서는 QueryClient와 QueryClientProvider가 필요합니다.

react가 제공하는 suspense 기능을 사용하고 싶으니

QueryClient의 인자를 전달하여 suspense:true를 주었습니다.

Suspense의 fallback으로 전달한 loading 컴포넌트는

쿼리를 받아오는 로딩 상태 동안 보여질 컴포넌트를 의미합니다.

PagenationComponent는 아직 만들지 않았으니 넘어갑시다.

import { useQuery, useQueryClient } from '@tanstack/react-query';
import React from 'react';

interface PokeType {
  name: string;
  url: string;
}

interface APIType {
  count: number;
  next: string;
  previous: string;
  results: PokeType[];
}

interface PagenationComponentProps {}

const getPagenation = async (page: number = 0) => {
  const response = await fetch(
    `https://pokeapi.co/api/v2/pokemon?limit=20&offset=${page}`,
  );
  return await response.json();
};

const PagenationComponent = ({}: PagenationComponentProps) => {
  const queryClient = useQueryClient();
  const [page, setPage] = React.useState(20);

  const pagenationQuery = useQuery<APIType>({
    queryKey: ['pagenation', page],
    queryFn: () => getPagenation(page),
    keepPreviousData: true,
    staleTime: 5000,
  });

  React.useEffect(() => {
    if (!pagenationQuery.isPreviousData) {
      queryClient.prefetchQuery({
        queryKey: ['pagenation', page + 20],
        queryFn: () => getPagenation(page + 20),
      });
    }
  }, [pagenationQuery.data, pagenationQuery.isPreviousData, page, queryClient]);

  console.log(pagenationQuery.data);

  return (
    <div>
      {pagenationQuery.data?.results.map((poke) => (
        <div key={poke.name}>{poke.name}</div>
      ))}
      <button
        onClick={() => {
          setPage((state) => Math.max(state - 20, 0));
        }}
      >
        prev button
      </button>
      <button
        onClick={() => {
          setPage((state) => state + 20);
        }}
      >
        next button
      </button>
    </div>
  );
};

export default PagenationComponent;

한파일에 모든것을 때려박은 형태로 작성해두었습니다.

하나하나 차근차근 분리해서 살펴보도록하겠습니다.

그 이전에 실제 동작이 어떻게 이루어지는지 확인하겠습니다.

페이지네이션 시연

네트워크창을 주목해서 보면 다음에 받아올 페이지를 미리 페칭해오는 것을 확인할  수 있습니다.

이제 하나하나 코드들을 살펴보도록 하겠습니다.

읽기만해도 코드가 이해되는 분들은 넘어가셔도 좋습니다.

const getPagenation = async (page: number = 0) => {
  const response = await fetch(
    `https://pokeapi.co/api/v2/pokemon?limit=20&offset=${page}`,
  );
  return await response.json();
};

useQuery의 queryFn에 들어갈 함수를 정의하는 부분입니다.

pokeapi는 limit과 offset을 설정해줄 수 있습니다.

20개씩 데이터를 받아올것이고 offset은 20씩 증가시키겠습니다.

만약 5, 10, 20 이런 형태로 선택할 수 있게 하고싶다면

limit에 들어갈 값도 동적으로 관리해주면 될것입니다.

 

다음은 받아오는 API의 타입을 정의해주겠습니다.

pokeapi의 data는 다음과 같은 형태로 보입니다.

count , next, previous, results 키가 존재하고

results 키는 name,url을 가진 객체들이 배열의 형태로 존재합니다.

interface PokeType {
  name: string;
  url: string;
}

interface APIType {
  count: number;
  next: string;
  previous: string;
  results: PokeType[];
}

따라서 간단하게 정의를 해주었습니다.

 

이제 리액트 컴포넌트 내부에서 useQuery로직을 작성하겠습니다.

  const queryClient = useQueryClient();
  const [page, setPage] = React.useState(20);

  const pagenationQuery = useQuery<APIType>({
    queryKey: ['pagenation', page],
    queryFn: () => getPagenation(page),
    keepPreviousData: true,
    staleTime: 5000,
  });

prefetch를 하기 위해 useQueryClient()를 사용하겠습니다.

또한 page값을 동적으로 관리해줄 것이니 useState로 상태를 만들어 관리해주겠습니다.

 

pagenationQuery는 useQuery의 반환값을 담을 것입니다.

리액트쿼리의 쿼리키는 마치 useEffect의 의존성 배열과도 비슷하게 생각할 수 있습니다.

keepPreviousData:true는 이전 데이터를 유지할 것인지를 묻는 옵션입니다.

staleTime은 데이터가 언제 stale해진 것으로 판단할지를 정하는 옵션입니다.

  React.useEffect(() => {
    if (!pagenationQuery.isPreviousData) {
      queryClient.prefetchQuery({
        queryKey: ['pagenation', page + 20],
        queryFn: () => getPagenation(page + 20),
      });
    }
  }, [pagenationQuery.data, pagenationQuery.isPreviousData, page, queryClient]);

이제 useEffect를 작성하고

if문에 해당되는 경우가 생기면 prefetchQuery를 날리는 로직을 작성하겠습니다.

isPreviousData는 boolean값이 담겨있는 프로퍼티입니다.

isPreviousData가 false일때 prefetching을 시도해주겠습니다.

이 useEffect가 있었기 때문에 앞서 본 시연화면에서

페이지가 넘어갈때마다 다음 페이지를 미리 prefetch할 수 있었던 것입니다.

      <button
        onClick={() => {
          setPage((state) => Math.max(state - 20, 0));
        }}

Math.max() 내장 메서드를 이용하여 state가 0미만으로 떨어지지 않게 관리해주었습니다.


마치며

이번 예제로 인해 어떤 느낌으로 페이지네이션을 구현할 수 있는지 알아보았으니

다음에는 좀 더 심화된 페이지네이션을 구현해보겠습니다.

반응형
저작자표시 비영리 (새창열림)

'react' 카테고리의 다른 글

react-query의 onError는 Deprecated 되었다.  (0) 2023.07.12
react-query를 이용해 pagenation을 구현해보자(2)  (2) 2023.06.30
forwardRef를 이용해 ref를 다는데... 타입은 어떻게 함?  (1) 2023.06.19
zod를 이용해 회원가입 폼을 만들어보기  (0) 2023.06.18
zod 라이브러리 사용법 간단히 익히기  (0) 2023.06.15
  1. pagenation이란?
  2. 필요한 기능 정의하기
  3. 환경 설정
  4. 마치며
'react' 카테고리의 다른 글
  • react-query의 onError는 Deprecated 되었다.
  • react-query를 이용해 pagenation을 구현해보자(2)
  • forwardRef를 이용해 ref를 다는데... 타입은 어떻게 함?
  • zod를 이용해 회원가입 폼을 만들어보기
냠냠맨
냠냠맨
프론트엔드 개발 전반을 다루는 기술 블로그입니다.
냠냠맨
React와 TypeScript를 좋아하는 개발자
냠냠맨
전체
오늘
어제
  • all category (434)
    • CMC (0)
    • best (11)
    • 년간회고 (1)
    • cheetsheet (15)
    • 프로젝트 회고 (3)
    • 서평 (3)
    • SEO Study (1)
    • 프로젝트 진행기 (10)
    • testcode (9)
    • yarnberry (7)
    • css (21)
    • typescript (15)
    • redux (7)
    • react (43)
    • Next.js (9)
    • Nestjs (3)
    • javascript (44)
    • programmers (67)
    • leetcode (41)
    • frontend (41)
    • backjoon (1)
    • Next.js Beta Docs 번역 (12)
    • TIL (15)
      • html (3)
    • Network (12)
      • 간단 정리 시리즈 (2)
      • 질답 준비 (0)
    • 자료구조와 알고리즘 (2)
    • CS (4)
      • OS (1)
    • 취업준비 (2)
    • zoom websocket (2)
    • talk (6)
    • 면접대비 (1)
    • 코드스테이츠 프론트 (5)
    • 간헐적 회고 (18)

블로그 메뉴

  • leetcode
  • programmers
  • javascript
  • html
  • css

공지사항

인기 글

태그

  • 테오의스프린트17기
  • 개발자
  • 테오의스프린트
  • 주니어개발자
  • 프론트엔드
  • frontend
  • JavaScript
  • 말풍선
  • teosprint
  • 코드스테이츠 #프론트엔드
  • LeetCode
  • CSS
  • 개발
  • border말풍선

최근 댓글

최근 글

hELLO · Designed By 정상우.
냠냠맨
react-query를 이용해 pagenation을 구현해보자
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.