무한 스크롤, useInfiniteQuery

이가리·2023년 3월 14일
0

useInfiniteQuery

const { refetch, data } = useQuery(["queryKey"], queryFn);
const { refetch, data } = useInfiniteQuery(["queryKey"], queryFn);

useQuery와 거의 똑같이 생겼다. queryKey와 queryFn를 입력해주면 된다. queryKey는 쿼리 데이터를 가져오는데 함께 호출되며 어떤 데이터를 가져올 것인지 식별하는데 사용된다.

data는 말 그대로 성공 시 받아오는 data, refetch는 해당 함수를 다시 쓰고 싶은 곳에 쓰면 된다. 나는 좋아요 기능 때문에 useEffect에서 쓰기 위해 필요했다. 명시적으로 아래와 같이 표기하면 좋다!

const { refetch: getBookmarkData, data: bookmarkData} = useInfiniteQuery(["queryKey"], queryFn);

일단 무한 스크롤을 위해서는 백엔드와 이야기가 우선되어야 한다!

막상 작업을 시작하려고 플레이 그라운드로 쿼리문을 봤는데.. input으로 보내야할 값이 lastResourceId?! 당황했다. 내가 어디서 이걸 뽑아와야하지? 그래서 바로 물어보니 첫 요청을 null로 하고, 이후 받아온 데이터에서 가장 마지막 값의 아이디가 lastResourceId 였다. 로직이 복잡해지고 페이지네이션에는 맞지 않는 것 같아 대화를 통해 page와 size를 input으로 바꾸기로 했다.


pageParam

pageParam을 통해 현재 어떤 페이지에 있는지 확인 할 수 있다. 기본값이 undefined이기 때문에 1로 설정했다. 데이터 조회 시 pageParam값을 api 파라미터 값으로 넣어 사용한다.

axios와 GraphQL를 같이 쓰기 때문에 post에 query와 variables를 추가했다.

통신이 성공하고 데이터가 있을 경우 구조분해 할당을 통해 데이터를 return 한다. hasNext의 boolean값으로 다음 페이지 여부를 알 수 있다. 이후 input으로 넣는 pageParam에 1을 더해주면 다음 요청 시 pageParam: 2로 요청을 보내게 된다. (반복)

const useBookmarkContents = input => {
  const getData = async input => {
      const response = await axios.post(GQL_API, {
        query: QUERY_BOOKMARK_OF_CONTENTS,
        variables: { input },
      });
      
      if (response?.data?.data) {
        const { bookmarkResult, hasNext } =
       response.data.data.bookmarkContent.results;
        return {
          bookmarkResult,
          hasNext,
          nextPage: input.page + 1,
        };
      }
    };

  const { refetch: getBookmarkData, data: bookmarkData, fetchNextPage, isFetchingNextPage, hasNextPage } = useInfiniteQuery(
      ['bookmarkContent'],
      ({ pageParam = 1 }) =>
        getData({ ...input, page: pageParam }),
      {
        getNextPageParam: bookmarkData =>
          bookmarkData?.hasNext ? bookmarkData.nextPage : undefined,
        getHasNextPage: bookmarkData => bookmarkData.hasNext ?? false,
        },
      }
    );
    
    return {
    	getBookmarkData, // refetch(Fn)
        bookmarkData,	 // data
        fetchNextPage,	 // 다음 페이지 불러오기(Fn)
        isFetchingNextPage, // 다음 페이지를 불러오는 중인지 여부(boolean)
        hasNextPage  // 다음 페이지 여부(데이터의 hasNext를 통해 boolean으로 관리)
    };
};

export default useBookmarkContents

useInView

무한 스크롤을 위해 'react-intersection-observer' 라이브러리의 useInView 훅를 사용했다.
이 훅을 통해 뷰포트에 보이는지 여부에 따라 inView라는 부울 값과 ref 객체를 반환하고, ref 객체로 요소를 참조할 수 있다.

isFechingNextPage는 useQuery의 isLoding 같은 역할로 현재 페이지 이후의 데이터를 가져오는 중인지 여부를 나타내는 boolean 값이다. true일 경우 다음 페이지를 가져오는 중이며 추가 스크롤을 하지 않도록 방지, 중복으로 데이터를 가져오는 문제도 방지 할 수 있다.

그래서 다음 페이지를 가져오는 중이 아니라면 div에 있는 ref를 통해 다음 페이지를 불러오도록 useEffect 안에 fetchNextPage()를 설정했다.(물론 다음 페이지가 있을 경우!)

const Component = () => {
  const { ref, inView } = useInView();
  const { getBookmarkData, bookmarkData, fetchNextPage, isFetchingNextPage, hasNextPage } = useInfiniteQuery({page: 1, size: 12})

  useEffect(() => {
      if(inView && hasNextPage) {
          fetchNextPage();
      }
  }, [inView])

  return (
    <div>
      {bookmarkData &&
        bookmarkData.pages?.map((page, idx) => {
          return (
            <React.Fragment key={idx}>
              {page && page.bookmarkResult.map(item => (
                <div key={item.itemId}>
                  <Card props={item} />
                </div>
              ))}
            </React.Fragment>
          );
        })}

       {!isFetchingNextPage && <div className="h-[0.5px]" ref={ref} />}
      </div>
  )
}

결과물

잦은 네트워크 요청 때문에 option 값 추가.

refetchOnWindowFocus: false, // 브라우저 창이 다시 포커싱될 때 새로운 데이터를 가져올지 여부
refetchOnMount: true, // 컴포넌트가 마운트될 때마다 데이터를 가져올지 여부
retry: 0, // 가져오기 요청이 실패했을 때 자동으로 재시도할 횟수를 결정

profile
절대로 할 수 있음

0개의 댓글