React

[React] 무한스크롤 - React Query useInfiniteQuery 로 구현하기

밍띠이 2024. 4. 21. 16:07
반응형

이번 프로젝트에서 인스타그램과 비슷한 UI를 기획하였기 때문에 피드에 무한 스크롤 기능을 추가해야 했다.

프로젝트에 이미 리액트 쿼리를 도입한 상태기 때문에 리액트 쿼리에서 제공하는 useInfiniteQuery를 사용하여 구현해 보고자 했다.

리액트 쿼리란?

 

[React] React Query 란?

1. React Query 소개 리액트 쿼리? 서버 상태를 관리하는 라이브러리 간단하게 알아보자 Client State vs. Server State ? 클라이언트 상태란 웹 브라우저 세션과 관련된 모든 정보를 의미한다. 예를들어 유

mingnol2.tistory.com

 

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

위의 명령어를 입력하면 필요한 라이브러리를 설치할 수 있다.

리액트 쿼리는 버전이 높아지며 Vue.js나 앵귤러 등도 지원하게 되어 이름이 tanstack으로 바뀌었다고 한다.

처음에 서로 다른건줄 알고 리액트 쿼리를 3버전으로 설치했었다..

현재는 v5 를 이용할 수 있다.

1.  useInfiniteQuery 란?

무한 스크롤을 구현하려면 useInfiniteQuery를 이용해야한다.

useQuery 와 비슷하지만 반환하는 데이터 모양이 다르다. infinite는 data와 page를 제공한다.

pages는 각 데이터 페이지를 나타내는 객체의 배열이다.

pageParams 는 page에서 사용하는 데이터를 나타내기 위한 변수이다.

검색된 쿼리의 키를 추적할 수 있다. 잘 사용하지는 않는다

 

아래와 같은 문법으로 사용 가능하다. 주석으로 설명을 달아놓은 것들 위주로 확인하면 된다.

const {
  fetchNextPage, // 다음페이지를 가져옴(마지막까지 스크롤 한 경우, 추가버튼을 클릭한 경우)
  fetchPreviousPage,
  hasNextPage, // 다음 페이지가 있는지, undefined면 false를 반환
  hasPreviousPage,
  isFetchingNextPage, // 데이터를 가져오는 중인지 구별
  isFetchingPreviousPage,
  ...result
} = useInfiniteQuery({
  queryKey, // 쿼리 키
  queryFn: ({ pageParam }) => fetchPage(pageParam), // 반환할 함수
  initialPageParam: 1, // 디폴트 파라메터
  ...options,
  getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
    lastPage.nextCursor, // 마지막 페이지 데이터나, 모든 페이지의 데이터에서 페이지를 가져옴
  getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) =>
    firstPage.prevCursor,
})

 

2.  useInfiniteQuery 호출 작성하기

예를들어 스타워즈 api (swapi)를 호출하여 본다고 하면 아래와 같이 작성할 수 있다.

https://swapi.dev/

 

SWAPI - The Star Wars API

What is this? The Star Wars API, or "swapi" (Swah-pee) is the world's first quantified and programmatically-accessible data source for all the data from the Star Wars canon universe! We've taken all the rich contextual stuff from the universe and formatted

swapi.dev

실제 첫번째 데이터가 있는 부분은 pages의 0번째 결과를 불러오면 된다.

import InfiniteScroll from "react-infinite-scroller";
import { Person } from "./Person";
import { useInfiniteQuery } from "@tanstack/react-query";

const initialUrl = "https://swapi.dev/api/people/";
const fetchUrl = async (url) => {
  const response = await fetch(url);
  return response.json();
};

export function InfinitePeople() {
  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
    queryKey: ["sw-people"],
    queryFn: ({ queryParam = initialUrl }) => fetchUrl(queryParam),
    getNextPageParam: (lastpage) => {
      return lastpage.next || undefined;
    },
  });
  console.log(data.pages[0].results[0]);
  return <InfiniteScroll />;
}

4. InfiniteScroll 컴포넌트

받아온 데이터를 InfiniteScroll 컴포넌트와 결합하여 보여줄 수  있다.

https://www.npmjs.com/package/react-infinite-scroller

 

react-infinite-scroller

Infinite scroll component for React in ES6. Latest version: 1.2.6, last published: 2 years ago. Start using react-infinite-scroller in your project by running `npm i react-infinite-scroller`. There are 505 other projects in the npm registry using react-inf

www.npmjs.com

npm install react-infinite-scroller --save

 

5. useInfiniteQuery 페칭과 에러 상태 처리

import InfiniteScroll from "react-infinite-scroller";
import { Person } from "./Person";
import { useInfiniteQuery } from "@tanstack/react-query";

const initialUrl = "https://swapi.dev/api/people/";
const fetchUrl = async (url) => {
  const response = await fetch(url);
  return response.json();
};

export function InfinitePeople() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetching,
    isLoading,
    isError,
    error,
  } = useInfiniteQuery({
    queryKey: ["sw-people"],
    queryFn: ({ queryParam = initialUrl }) => fetchUrl(queryParam),
    getNextPageParam: (lastpage) => {
      return lastpage.next || undefined;
    },
  });

  if (isLoading) {
    return <div className="loading">🔥 로딩중....</div>;
  }

  if (isError) {
    return <div className="error">에러 발생 {error.toString()}</div>;
  }

  return (
    <>
      {isFetching && <div>💥 로딩중 ... </div>}
      <InfiniteScroll
        loadMore={() => {
          if (!isFetching) {
            fetchNextPage();
          }
        }}
        hasMore={hasNextPage}
        loader={
          <div className="loader" key={0}>
            Loading ...
          </div>
        }
      >
        {data.pages.map((pageData) => {
          return pageData.results.map((person) => {
            return (
              <Person
                key={person.name}
                name={person.name}
                hairColor={person.hair_color}
                eyeColor={person.eye_color}
              />
            );
          });
        })}
      </InfiniteScroll>
    </>
  );
}

페이지 끝에서 다음 페이지가 있을 경우 lastpage의 next 값을 가져와 다음 param으로 적용한다.  undefined인 경우 hasNextPage 값이 false로 반환 된다.

 

6. 요약

 

+ 양방향 스크롤링도 가능하다

중간부터 시작할때, 이때는 previous 이용하면 된다.

리액트 쿼리는 무한스크롤 시 많은 기능을 제공한다.

페이지네이션을 하거나 컴포넌트에서 페이지를 처리할 수 있다.

pageParam: 가져와야 할 다음 페이지, getNextPageParam옵션을 통해 관리된다. lastPage/allPages 매개변수를 사용한다. 반환된 데이터 양식에 따라 선택하면 된다. 

hasNextPage: boolean 값, pageParam이 정의되어 있으면 true, undefined 이면 false를 반환한다.

때문에 더 불러올 수 있는 데이터가 있는지 확인이 가능하다.

fetchNextPage: 컴포넌트가 데이터를 불러와야 할때를 결정하고 hasNextPage 값으로 언제 그만 가져올 지 지정해 줄 수 있다.

 

 

 

반응형