[React Query] Section3- Infinite Queries for Loading Data “Just in Time”(”동적" 데이터 로드를 위한 무한 쿼리)

이해용·2022년 8월 28일
0
post-thumbnail

※ 해당 섹션은 3버전 react query로 4버전과 다른 부분이 있을 수 있습니다.

Infinite Scroll - Star Wars API

  • Infinite scroll
    • fetch new data “just in time” as user scrolls (사용자가 스크롤할 때마다 새로운 데이터를 가져오는 것이다.)
    • more efficient than fetching all data at once(모든 데이터를 즉시 가져오는 것 보다 더 효과적이다.)
  • Fetch new data when…
    • user clicks a button
    • user scrolls to certain point on the page ⇒ choice

useInfiniteQuery

  • Requires different API format than pagination
  • Hence new project!
  • Pagination
    • track current page in component state
    • new query updates page number
  • useInfiniteQuery tracks next query
    • next query is returned as part of the data

ex) API

{
	"count": 37,
	"next": "http: //swapi.dev/api/specie/?page=2",
	"previous": null,
	"results": [...]
}

Shape of useInfiniteQuery Data

  • Shape of data different than useQuery
  • Object with two properties:
    • pages (객체의 배열인 pages)
    • pageParams (각 페이지의 매개변수가 기록되어있다.)
  • Every query has its own element in the pages array
  • pageParams tracks the keys of queries that have been retrieved(pageParms는 검색되는 쿼리들의 키를 추적한다.)
    • rarely used, won’t use here

useInfiniteQuery Syntax

  • pageParams is a paremeter passed to the queryFn (pageParam은 쿼리 함수에 전달되는 매개변수이다)
    • useInfiniteQuery(”sw-people”, ({ pageParam = defaultUrl }) ⇒ fetchUrl(pageParam)
      “sw-people” → query key
    • Current Value of pageParam is maintained by React Query
  • useInfiniteQuery options
    • getNextpageParam: (lastPage, allPages)
      • Update pageParam
      • Might use all of the pages of data(allPages)
      • Will use just the lastPage of data (specifically the next property)

useInfiniteQuery Return Object Properties

  • fetchNextPage
    • function to call when the user needs more data
  • hasNextPage
    • Based on return value of getNextPageParam
    • If undefined, no more data
  • isFetchingNextPage
    • For displaying a loading spinner (loading spinner는 로딩을 표시하는 회전하는 모양이다.)
    • We’ll see an example of when it’s useful to distinguish between isFetching and isFetchingNextPage

The Flow

Component mounts → Fetch first page → getNextPageParam, Update pageParamhasNextPage? →(yes) user scrolls / clicks button fetchNextPagegetNextPageParam, Update pageParam →(no) done!

import { useInfiniteQuery } from "@tanstack/react-query";

...
export function InfinitePeople() {
  const { data, fetchNextPage, hasNextPage } = useInfiniteQuery(
    ["sw-people"], // queryKey
    ({ pageParam = initialUrl }) => fetchUrl(pageParam),
    {
      getNextPageParam: (lastPage) => lastPage.next || undefined, // (lastPage, allpages) 이나 2페이지 밖에 있지 않아 lastPage만 사용한다.
    }
  );
...

React Infinite Scroller

  • Works really nicely with useInfiniteQuery
  • Populate two props for InfiniteScroll component:
    • loadMore={fetchNextPage}
    • hasMore={hasNextPage}
  • Component takes care of detecting when to load more
  • Data in data.pages[x].results

loadMore?

required - yes
type - function
description - A callback when more items are requested by the user. Receives a single parameter specifying the page to load e.g. function handleLoadMore(page) { / load more items here / } }

hasMore?

required - no
type - boolean
default - false
desdcrtiption - Whether there are more items to be loaded. Event listeners are removed if false.

ex)
...
return (
    <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}
            ></Person>
          );
        });
      })}
    </InfiniteScroll>
  );

결과

page의 타입을 지정해주기 위해 isLoading과 isError를 사용하여 나타내주어야한다.

isLoading, isError, isFetching 활용

isLoadin코드

...
const {
    data,
    fetchNextPage,
    hasNextPage,
    isLoading,
    isError,
    error,
  } = useInfiniteQuery(
    ["sw-people"],
    ({ pageParam = initialUrl }) => fetchUrl(pageParam), // 초기 url로 시작해서 pageParam 값으로 fetchUrl을 실행한다.
    {
      getNextPageParam: (lastPage) => lastPage.next || undefined, // 이전페이지(lastPage) 의 다음프로퍼티를 불러와 새 페이지 데이터가 있을 때마다 PageParam에 지정해준다. SWAPI에서 지정한 대로 함수의 값이 null인 경우에는 undefined를 둔다. 함수 값이 지정되지 않으면 hasNextPage의 값도 거짓이 된다.
    }
  );

  if (isLoading) return <div className="loading">Loading...</div>;

  if (isError) return <div>Error! {error.toString()}</div>;
...

getNextPageParam

getNextPageParam: (lastPage, allPages) => unknown | undefined
When new data is received for this query, this function receives both the last page of the infinite list of data and the full array of all pages.(쿼리에서 새로운 데이터를 받아 올 때, 함수는 데이터의 infinite list의 마지막 페이지와 모든 페이지의 전체 배열 둘 다 받는다.)
It should return a single variable that will be passed as the last optional parameter to your query function.(쿼리 함수에서 마지막 선택적인 파라미터가 전달되는 단일 변수를 리턴해야한다.)
Return undefined to indicate there is no next page available. (다음페이지가 이용가능한지 없는지를 나타내는 'undefined'를 반환해야한다.)

결과 화면

위처럼 진행하면 결과는 잘 나오지만 스크롤을 내릴 때 loading중이라는 문구가 나타나지 않아 isFetching을 사용해본다.

isFetching 코드

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

  if (isFetching) return <div className="loading">Loading...</div>;

  if (isError) return <div>Error! {error.toString()}</div>;
...

결과 화면

  • 데이터는 있지만 스크롤이 원위치되는 이유는 새로운 페이지를 열어야 할 때 조기반환이 실행되기 때문입니다.
  • 이 문제를 해결하기 위해 isLoading과 isFetching을 같이 사용한다면 원하는 결과를 볼 수 있다.
  • isFetching을 조기반환 시키지 않고 조건부 렌더링을 사용하여 infinite Scroll에 loading… 을 나타나게 지정해준다.

isLoading과 isFetching을 활용한 코드

export function 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 className="loading">Loading...</div>;

  if (isError) return <div>Error! {error.toString()}</div>;

  return (
    <>
      {isFetching && <div className="loading">Loading...</div>} // 조건부 렌더링을 사용하여 isFetching이 진행되면 Loading이라는 문구가 오른쪽 상단에 나타나게 설정해준다.
      <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>
    </>
  );
}

최종 결과 화면

Infinite Scroll Summary

InfiniteScroll

<InfiniteScroll loadMore={fetchNextPage} hasMore={hasNextPage}>
  • loadMore의 fetchNextPage는 useInfiniteQuery에 영향을 받아 어떤 쿼리 함수든 pageParam 값을 쓰게 되고 pageParam값은 데이터가 추가되면 갱신됩니다.

  • hasMore 함수는 InfiniteScroll 컴포넌트가 계속 데이터를 불러올지를 결정하는 역할을 해요. 이 함수는 hasNextPage 프로퍼티의 영향을 받고 pageParam이 정의되지 않아 거짓이 되는 경우도 lastPage.next 를 통해 다음 프로퍼티가 없거나 프로퍼티가 거짓인 경우도 정의했습니다.

useQuery와 useInfiniteQuery의 차이

  • useQuery와 useInfiniteQuery의 함수 결과는 다릅니다.

  • useQuery는 쿼리 함수의 결과를 그대로 출력합니다.

  • useInfiniteQuery 가 반환하는 데이터에 든 프로퍼티 중 사용하는 건 페이지 프로퍼티고 여기에는 pageParam이 실행될 때마다 필요한 데이터 배열이 들어있습니다.

  • React Query manages
    • pageParam for next page to be fetched (pageParam 배열은 가져와야 할 다음 페이지를 나타내는데
      • getNextPageParam option (getNextPageParam 을 통해 관리되며)
      • could be from lastPage, or allPages (두 가지 매개변수를 사용합니다.)
  • hasNextPage (pageParam은 또 hasNextPage 값을 제어하는데)
    • boolean indicating whether pageParam is undefined (이 불리언 값은 pageParam이 정의되어 있으면 참, 정의되지 않았으면 거짓을 반환해서 더 불러올 데이터가 있는지를 확인할 수 있습니다.)
  • Component handles calling fetchNextPage (fetchNextPage는 컴포넌트의 영향을 받기 때문에 컴포넌트가 데이터를 불러와야 할 때를 결정하고 hasNextPage 프로퍼티로 데이터를 그만 가져오게 할 수 있습니다.)
    • use hasNextPage value to determine when to stop

reference
https://www.udemy.com/course/learn-react-query
https://tanstack.com/query/v4/docs/installation?from=reactQueryV3&original=https://react-query-v3.tanstack.com/installation
https://tanstack.com/query/v4/docs/quick-start?from=reactQueryV3&original=https://react-query-v3.tanstack.com/quick-start
https://tanstack.com/query/v4/docs/devtools?from=reactQueryV3&original=https://react-query-v3.tanstack.com/devtools
https://tanstack.com/query/v4/docs/reference/useInfiniteQuery

profile
프론트엔드 개발자입니다.

0개의 댓글