리액트 쿼리(v3) - useInfiniteQuery

hwisaac·2023년 2월 5일
0

리액트쿼리

목록 보기
3/5

useInfiniteQuery

  • 이미 가지고 있는 데이터 세트에다가 추가적으로 데이터를 로드 해야될 경우 사용한다. (ex: 무한스크롤 or 페이지네이션 )
  • useInfiniteQuery 를 사용할 때 알아야 하는 사실들
    1. data 는 현재 infinite query data를 포함하는 객체이다.
    2. data.pages 는 패치받아온 페이지들로 이뤄진 배열이다.
    3. data.pageParams 는 페이지들을 패치할때 사용한 page params 로 이뤄진 배열이다.
    4. fetchNextPagefetchPreviousPage 눈 둘다 'now available'인 함수들
    5. getNextPageParamgetPreviousParam 은 만약 추가로 더 가져올 데이터가 있는지 판단하는 옵션들이다. 이 정보는 쿼리 함수에 추가 파라미터로서 제공된다. (쿼리함수는 fetchNextPage와 fetchPreviousPage 호출로 덮어쓸 수도 있다.)
    6. hasNextPage/hasPreviousPage 는 'now available'인지 나타내는 boolean 이다.(getNextPageParam/getPreviousPageParamundefined를 리턴하지 않을 경우 true가 된다는 말)
    7. isFetchingNextPageisFetchingPreviousPage 는 백그라운드의 refresh state 인지 state를 추가로 load할 수 있는지를 나타내는 boolean이다

예시

// cursor 인덱스로 한번에 페이지를 세개씩 가져온다고 할 때

fetch('/api/projects?cursor=0')
 // { data: [...], nextcursor: 3}
 fetch('/api/projects?cursor=3')
 // { data: [...], nextcursor: 6}
 fetch('/api/projects?cursor=6')
 // { data: [...], nextcursor: 9}
 fetch('/api/projects?cursor=9')
 // { data: [...] }
  • 이러한 정보로 Load More 버튼 같은 걸 만들 수 있다.

주의!

getNextPageParam에서 리턴한 pageParam 데이터를 덮어쓰기를 원하는게 아니라면, fetchNextPage를 콜백 인자로 넣어서 호출하지 말자!

예를들어, <button onClick={fetchNextPage} /> 이런식으로 쓰지 말자.

import { useInfiniteQuery } from 'react-query'
 
 function Projects() {
   const fetchProjects = ({ pageParam = 0 }) =>
     fetch('/api/projects?cursor=' + pageParam)
 
   const {
     data,
     error,
     fetchNextPage,
     hasNextPage,
     isFetching,
     isFetchingNextPage,
     status,
   } = useInfiniteQuery('projects', fetchProjects, {
     getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
   })
 
   return status === 'loading' ? (
     <p>Loading...</p>
   ) : status === 'error' ? (
     <p>Error: {error.message}</p>
   ) : (
     <>
       {data.pages.map((group, i) => (
         <React.Fragment key={i}>
           {group.projects.map(project => (
             <p key={project.id}>{project.name}</p>
           ))}
         </React.Fragment>
       ))}
       <div>
         <button
           onClick={() => fetchNextPage()}
           disabled={!hasNextPage || isFetchingNextPage}
         >
           {isFetchingNextPage
             ? 'Loading more...'
             : hasNextPage
             ? 'Load More'
             : 'Nothing more to load'}
         </button>
       </div>
       <div>{isFetching && !isFetchingNextPage ? 'Fetching...' : null}</div>
     </>
   )
 }

사용예시2

import {  useSearchParams } from "react-router-dom";
import { useInfiniteQuery } from "react-query";

// fetcher 함수
function searchData(
  category: string,
  keyword: string,
  page: number
) {
  const json = await fetch(
    `${BASE_PATH}/search/${category}?api_key=${API_KEY}&query=${keyword}&page=${page}`
  ).then((response) => response.json());
  
  return json;
}


export default function Search() {
// 검색어를 제출하면 Search 가 랜더링 되는데, 검색어 키워드를 가져온다.
const [searchParams, setSearchParams] = useSearchParams();
const keyword = searchParams.get("keyword");

// 랜더링 될 영화들이 담길 state
const [movies, setMovies] = useState();

// useInfiniteQuery 적용
const {
    data: movieInfiniteData,
    fetchNextPage: movieFetchNextPage,
    hasNextPage: movieHasNextPage,
    isFetchingNextPage: movieIsFetchingNextPage,
  } = useInfiniteQuery(
    ["searchMovie", keyword],
    ({ pageParam = 1 }) => searchData("movie", keyword, pageParam),
    {
      getNextPageParam: (lastPage, pages) => {
        const { page, total_pages } = lastPage;
        return page < total_pages ? page + 1 : undefined;
      },

      onSuccess: (movieInfiniteData) => {
        let result = [];
        for (let page of movieInfiniteData.pages) {
          result = [...result, ...page.results];
        }
        setMovies(result);
      },
    }
  );

return (
<Wrapper>
  <Title>Movie SEARCH: {keyword}</Title>

  {movies && <SearchedResults results={movies} />}

  <MoreButton
    onClick={() => movieFetchNextPage()}
    disable={!movieHasNextPage || movieIsFetchingNextPage}>
    {movieIsFetchingNextPage
      ? "추가 패칭중"
      : movieHasNextPage
      ? "영화 더보기"
      : "남은 영화가 없습니다."}
  </MoreButton>
</Wrapper>
);
}

useInfiniteQuery Hook

const {
   fetchNextPage,
   fetchPreviousPage,
   hasNextPage,
   hasPreviousPage,
   isFetchingNextPage,
   isFetchingPreviousPage,
   ...result
 } = useInfiniteQuery(queryKey, ({ pageParam = 1 }) => fetchPage(pageParam), {
   ...options,
   getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
   getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
 })

옵션

  • useInfiniteQuery 의 옵션들은 useQuery 훅이랑 같은데, 아래 내용이 추가된다.
옵션명타입default설명
queryFn(context: QueryFunctionContext) => PromisedefaultQueryFn- defaultQueryFn이 없는 경우에 필수
- 데이터 요청할때 사용하는 쿼리 함수
QueryFunctionContextqueryKeypageParam:unknown\|undefined변수를 갖는 객체이다
- datapageParam(getNextPageParam을 사용시)을 리턴해야 한다.
getNextPageParam(lastPage, allPages) => unknown | undefined- 쿼리의 신규데이터를 받으면 getNextPageParam 함수에 infinite data list의 마지막 페이지와 페이지들의 full array가 인자로 전달된다
- single variable을 리턴해야 한다. 이 단일 변수는 쿼리함수의 마지막 옵셔널 파라미터에 전달된다.
- undefined를 리턴시키면 다음페이지가 없음을 가리킨다.
getPreviousPageParam(firstPage, allPages) => unknown | undefined- 쿼리의 신규데이터를 받으면 getPreviousParam 함수에 infinite data list의 첫 페이지와 모든 페이지가 인자로 전달된다.
- single variable 을 리턴해야 되는데, 이 단일 변수는 쿼리의 마지막 옵셔널 파라미터에 전달된다.
-undefined를 리턴면 이전 페이지가 없다는 걸 가리킨다.

리턴값

  • useInfiniteQuery 의 리턴은 useQuery 훅이랑 동일한데, 아래 내용이 추가된다.
옵션명타입default설명
data.pagesTData[]모든 페이지를 담은 배열
data.pageParamsunknown[]모든 page params를 담은 배열
isFetchingNextPagebooleanfetchNextPage로 다음 페이지가 패칭되는 동안 true
isFetchingPreviousPagebooleanfetchPreviousPage로 다음 페이지가 패칭되는 동안 true
fetchNextPage(options?: FetchNextPageOptions) => Promisetrue이 함수는 results의 다음 page를 패치하게 해준다.
options.pageParam: unknown는 특정한 page param을 사용해서 패칭하게 해준다.
options.cancelRefetch: booleantruefetchNextPage를 반복 호출하면 fetchPage도 매번 호출되고 또한 이전의 호출은 무시된다. falsefetchNextPage를 반복호출해도 첫번째 호출이 resolved가 되기전까지 기다린다.
fetchPreviousPage(options?: FetchPreviousPageOptions) => Promise위와 비슷
hasNextPagebooleannext page를 fetch할게 있으면 가 된다.(getNextPageParam옵션을 통해 정해짐)
hasPreviousPageboolean위와 비슷

0개의 댓글