[React] TanstackQuery를 활용한 무한 스크롤 구현

intersoom·2023년 9월 16일
0

리액트

목록 보기
5/6
post-thumbnail
post-custom-banner

현재 진행중인 슈게팅이라는 프로젝트에서 무한 스크롤을 구현할 일이 생겼다!

해당 블로그에서 무한스크롤 관련된 정보를 읽고
tanstackQuery에서 제공하는 useInfiniteQuery를 써보고 싶어서 무한 스크롤 구현할 일만 기다리고 있었는데,,

마침 잘 되었다 싶어서 사용해보았다!

import { useInfiniteQuery } from '@tanstack/react-query'

import getAnimals from '../apis/getAnimals'
import { AnimalType, GenderType } from '../types/explore.type'
import { AnimalsResponse } from '../types/getAnimals.type'
import { animalClientToServer } from '../utils/animalUtil'

export const useGetAnimals = (gender: GenderType, animals: AnimalType) => {
  return useInfiniteQuery<AnimalsResponse>({
    queryKey: ['getAnimals', gender, animals],
    queryFn: ({ pageParam = 0 }) => getAnimals(gender, animalClientToServer(animals), pageParam),
    getNextPageParam: (lastPage, allPage) => {
      return lastPage.page.number !== allPage[0].page.totalPages
        ? lastPage.page.number + 1
        : undefined
    },
    staleTime: 60000 * 3,
  })
}

📍 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.
  • 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.

출처: https://tanstack.com/query/v4/docs/react/reference/useInfiniteQuery

해당 함수는

  • lastPage: 인피니트 리스트의 제일 최근의 페이지에 담긴 정보를 넘겨줌
    (해당 정보의 타입은 응답 타입으로 지정한 타입임 따라서 위의 코드에서는 AnimalsResponse)

  • allPages: 인피니트 리스트의 전체를 넘겨줌
    (해당 정보의 타입은 AnimalsResponse[])

그리고 더 이상 로드 할 게 없다면, getNextPageParamundefined 값을 넘겨주면된다

📍 어떻게 적용했는가?

내가 적용한 방식에 대해서 추가적으로 설명해보겠다.

먼저, 서버 데이터가 넘어오는 구조는 다음 그림과 같다

  • number: 현재 페이지 번호
  • size: 한 페이지 게시물 몇 개 불러오는지 (default: 16개)
  • totalElements: 총 게시물 갯수
  • totalPages: 총 페이지 갯수
getNextPageParam: (lastPage, allPage) => {
      return lastPage.page.number !== allPage[0].page.totalPages
        ? lastPage.page.number + 1
        : undefined
    },

→ 그래서 나는 페이지의 현재 넘버와 총 페이지 수를 비교해서 같이 않을 경우, + 1한 값을 return 해주었다
→ 그리고 같을 경우에는 undefined를 return 하여서 무한 스크롤을 멈춘다

이제 해당 훅을 실제 코드에 어떻게 적용했는지 알아보자!

const { data, fetchNextPage, hasNextPage, isLoading, isError } = useGetAnimals(
    currentExploreFilter.gender,
    currentExploreFilter.gender === 'female'
      ? currentExploreFilter.femaleAnimal
      : currentExploreFilter.maleAnimal
  )

이렇게 요소들을 불러와준다

<div className="flex flex-col h-full overflow-auto">
	<InfiniteScroll
		pageStart={0}
		loadMore={() => fetchNextPage()}
		hasMore={hasNextPage}
		loader={
			<div className="loader" key={0}>
				Loading ...
			</div>
		}
		useWindow={false}
	>
	<div className="flex w-screen justify-center">
		<div className="flex flex-wrap gap-5 justify-start self-center w-[342px]">
			{data?.pages.map((page) => {
				return page.users.map((item, index) => (
					<InformationTypeButton
						nickname={item.nickName}
						mbti={item.mbti}
						key={index}
						animal={animalServerToClient(item.animals)}
						gender={item.gender.toLowerCase() as GenderType}
						content={item.introduce}
						onButtonClick={handlePopupSelected}
					></InformationTypeButton>
				))
			})}
		</div>
	</div>
	</InfiniteScroll>
</div>

'react-infinite-scroller’라는 라이브러리를 활용하면 직접 스크롤 이벤트를 추적해서 새로운 함수를 불러오지 않아도 된다! 매우 편리…

해당 라이브러리에는 쓰로틀링이 내장되어있다고 한다

쓰로틀링 관련 벨로그 링크
react-infinite-scroller npm 사이트

⛔️ 주의할 점(1)typescript를 사용하면 types도 같이 설치해줘야한다 ⛔️

yarn add @types/react-infinite-scroller

⛔️ 주의할 점(2)는 부모의 높이에 height 설정 + overflow: auto 설정을 해줘야한다⛔️

<div className="flex flex-col h-full overflow-auto">
	<InfiniteScroll
		pageStart={0}
		loadMore={() => fetchNextPage()}
		hasMore={hasNextPage}
		loader={
			<div className="loader" key={0}>
				Loading ...
			</div>
		}
		useWindow={false}
	>

사용 방법은 다음과 같이 훅 호출을 통해서 불러온 함수들을 이렇게 넣어주기만 하면 된다!

pageStart={0}
loadMore={() => fetchNextPage()}
hasMore={hasNextPage}

다음은 데이터를 이용해서 컴포넌트들을 불러오는 코드이다

{data?.pages.map((page) => {
	return page.users.map((item, index) => (
		<InformationTypeButton
			nickname={item.nickName}
			mbti={item.mbti}
			key={index}
			animal={animalServerToClient(item.animals)}
			gender={item.gender.toLowerCase() as GenderType}
			content={item.introduce}
			onButtonClick={handlePopupSelected}
		></InformationTypeButton>
	))
})}
const { data 
} = useGetAnimals(
    currentExploreFilter.gender,
    currentExploreFilter.gender === 'female'
      ? currentExploreFilter.femaleAnimal
      : currentExploreFilter.maleAnimal
  )

위와 같이 훅에서 호출한 data에서는 pages / pageParams를 반환한다

그래서 pages에 대한 map을 돌리고 거기서 page에 대한 map을 한 번 더 돌려야 한다!

그러면 원하는대로 무한 스크롤이 아주 잘 작동할 것이다!!

post-custom-banner

0개의 댓글