React Query - useInfiniteQuery

박정호·2023년 1월 11일
2

React Query

목록 보기
5/14
post-thumbnail

🚀 Start

직전에 쇼핑몰 프로젝트를 진행하며 상품들을 무한스크롤하여 표현할때 React Query의 Infinite Scroll 기능을 사용하였는데 당시에는 구현하는데 급급했으니 이번에 제대로 한번 알아보도록 하자.

Infinite Scroll은 간단하게 설멍하면 사용자가 스크롤할 때마다 새로운 데이터를 가져오는 것이다. 따라서 모든 데이터를 한번에 가져오는 것보다 훨씬 효율적이다.

새로운 데이터를 가져오는 방법은 두가지가 있다.

  • 사용자가 직접 버튼을 클릭시 새로운 데이터를 요청
  • 페이지의 특정 지점까지 스크롤시 자동으로 새로운 데이터를 요청


⭐️ useInfiniteQuery

useInfiniteQuery는 파라미터 값만 변경하여 동일한 useQuery를 무한정 호출할 때 사용

useInfiniteQuery는 다음 쿼리가 뭘지 추적하게 된다. 이 경우에는 다음 쿼리가 데이터의 일부로 반환된다.


useInfiniteQuery의 사용법은 전반적으로 useQuery와 동일하지만, useInfiniteQueryuseQuery를 비교해보며 useInfiniteQuery의 기능을 살펴보자.

반환 객체에서 반환된 데이터 프로퍼티의 형태가 다르다.

useQuery에서의 데이터는 단순히 쿼리함수에서 반환된 데이터를 말한다.

useInfiniteQuery에서는 두개의 프로퍼티를 가지고 온다.

하나는 데이터 페이지 객체의 Array 페이지이고 이때 각 페이지에 있는 각 요소가 바로 useQuery를 통해 받아오는 데이터들이다.

또 다른 하나는 각 페이지의 매개변수가 기록되는 pageParams이다. pageParams는 검색된 쿼리의 키를 추적하기 때문이다. 모든 쿼리는 페이지 배열에 고유한 요소를 가지고 있고 그 요소는 해당 쿼리에 대한 데이터에 해당한다. 따라서 페이지가 진행되면서 쿼리도 변하게 되어 있다.

pageParam은 useInfiniteQuery가 현재 어떤 페이지에 있는지를 확인할 수 있는 파라미터 값


구문이 다르다.

useInfiniteQuery 쿼리함수가 실행되는 동안 매개변수, 객체를 구조분해하는 pageParam을 사용한다. 그리고 첫번째 Url로 정의한 Url을 기본값으로 설정한다. 따라서 함수는 defaultUrl을 기본값으로 하는 pagParam을 사용해서 fetchUrl을 실행하게 한다.
pageParam은 다음과 같이 쿼리함수의 파라미터 값에서 확인 가능하다.

useInfiniteQuery('쿼리키', ({pageParam = defaultUrl}) => fetchUrl(pageParam))

조금만 더 쉽게 이해해보자면, pagination 기능을 구현할때는 useQuery의 경우 useState의 상태값을 통해 페이지의 값을 유지시켰다면, useInfiniteQuery의 경우 pageParam을 통해서 현재값을 유지시킨다.


useInfiniteQuery의 옵션

  • getNextPageParam

    • 다음 페이지로 가는 방식을 정의하는 함수
    • 마지막 또는 모든 페이지에 대한 데이터를 다룬다.
    • 다음 api를 요청할 때 사용될 pageParam값을 정할 수 있다.
  • fetchNextPage

    • 사용자가 더 많은 데이터를 요청할 때 호출하는 함수
  • hasNextPage

    • getNextPageParam의 반환값을 기반으로 하는 함수
    • 마지막 쿼리의 데이터를 어떻게 사용할지 지시한다.
    • undefined의 경우 더 이상 데이터가 없다는 뜻
  • isFetchingNextPage

    • 다음 페이지를 가져오는건지, 일반적인 패칭인지를 구별

💡 참고하자
👉 [React] React Query의 useInfiniteQuery에 대해 알아보기



🌊 Flow

1️⃣ 컴포넌트 mounts

이 시점에는 useInfiniteScroll이 반환된 객체의 data 프로퍼티가 아직 정의되어있지 않다. 왜냐하면 아직 쿼리를 만들지 않았기 때문

const {data} = useInfiniteScroll( ... )

👉 data : undefined

2️⃣ 첫번째 페이지 fetching

그 다음 useInfiniteScroll은 쿼리함수를 사용해서 첫 페이지를 가져온다.

쿼리함수는 useInfiniteScroll의 첫번째 인수이고 pageParam을 인수로 받는다.

const {data} = useInfiniteScroll({pageParams = defaultUrl} => ...)

👉 data.pages[0]: {...} // 첫번째 페이지
👉 pageParam : default                                 

3️⃣ getNextPageParam & pageParam 업데이트

데이터가 반환된 후 React Query가 getNextPageParam을 실행하고, getNextPageParam가 pageParam을 업데이트한다.

// 이때는 API에서 반환된 데이터양식에 따라 lastPage, Allpage를 고르면 된다.
getNextPageParam: (lastPage, allPage) => ...

👉 pageParam : "http://......../?page=2"

4️⃣ hasNextPage

React Query가 hasNextPage의 값을 결정하는 방식은 pageParam이 정의되어 있는지 아닌지에 따른다. 즉, 다음으로 올 페이지가 유무에 따라 함수가 사용된다.

5️⃣ fetchNextPage

만약 사용자가 버튼을 클릭하거나, 스크롤하는 등의 fetchNextPage를 트리거할만 행동을 했다면 다음 요소를 업데이트하거나 페이지 배열에 다음 요소를 추가한다.

👉 data.pages[1]: {...} // 다음 페이지



👍 React-Infinite-Scroller

이 패키지는 창과 스크롤 가능한 요소를 모두 지원하여 간단하고 가벼운 무한 스크롤 페이지 또는 요소를 만들 수 있다. 그리고 무엇보다 useInfiniteQuery와 호환이 잘된다.(공식문서)

  • loadMore : 데이터가 더 필요할 때 불러와 useInfiniteQuery의 fetchNextPage 함수값을 이용한다.

  • hasMore : hasNextPage와 같이 useInfiniteQuery에서 나온 객체를 해체한 값을 이용한다.

<import InfiniteScroll from 'react-infinite-scroller';

...

< InfiniteScroll 
    pageStart = { 0 } 
    loadMore = { loadFunc } 
    hasMore = { true  ||  false } 
    loader = { <div className="loader" key={0}>Loading...</div>} 
> 
    { items } // <-- 로드하려는 콘텐츠
< / InfiniteScroll >

무한 스크롤 컴포넌트는 스스로 페이지의 끝에 도달했음을 인식하고 fetchNextPage를 불러오는 기능을 한다.

이전에는 Intersection Observer API를 사용하여 페이지의 끝에 도달하는 것을 관찰하여 새로운 데이터를 불러왔었다.(참고)

이번에는 React-Infinite-Scroller를 알게되어 사용해볼 기회가 되었다.

단, 해당 패키지와 버전이 안맞는 문제가 있으므로 legacy-peer-deps 설치는 필수!(참고)

npm install --save --legacy-peer-deps


⌨️ Real Code

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

export const InfinitePeople = () => {
  const {data, fetchNextPage, hasNextPage, isLoading, isFetching, isError, error } = useInfiniteQuery(
    "sw-people",
    ({pageParam = initialUrl}) => fetchUrl(pageParam),
    {
      getNextPageParam : (lastPage) => lastPage.next || undefined
    }
  );

  if (isLoading) return <div style={{position:'fixed', top:'5px', right:'5px'}}>로딩중</div>
  if (isError) return <div> 에러 : {error.toString()}</div>

  return (
  <>
   {isFetching && <div style={{position:'fixed', top:'5px', right:'5px'}}>로딩중</div>}
    <InfiniteScroll loadMore={fetchNextPage} hasMore={hasNextPage}>
      {
        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>
  </>
  )

}

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글