본문 바로가기
React

React Query - useInfiniteQuery 사용법

by 오늘부터개발시작 2022. 8. 15.

 

 

 

 

 

React Native를 사용해서 앱을 만드는 중에 infinite scroll 리스트를 구현할 필요가 있었다. react query를 사용하던 중이어서 state를 따로 관리해서 페이징을 구현해야하나 고민하던 중에 react query에서 useInfiniteQuery라는 hook을 지원하는 것을 찾았다. 오늘은 useInfiniteQuery를 어떻게 사용하는지에 대해서 간단히 설명해보고자 한다.

 

useInfiniteQuery

useInfiniteQuery는 페이징을 구현할 때도 굳이 state를 사용하지 않고 손쉽게 구현할 수 있도록 도와준다. api 요청은 보통 방식과 동일하게 몇 페이지인지 request param으로 보내고 응답이 왔을 때 다음 페이지가 몇 페이지인지 혹은 더 이상 페이지가 없는지 처리해주면 된다. useInfiniteQuery hook을 사용하는 방법과  html 코드에 어떻게 적용하는지 예시를 통해 좀 더 자세히 설명해보도록하겠다.

 

const getRequestsQuery = useInfiniteQuery('queryKey', ({ pageParam = 0 }) => {
    return RequestService.getRequests(pageParam);
}, {
getNextPageParam: (lastPageData: PageResult) => {
  return lastPageData.last ? undefined : lastPageData.number + 1;
},
onSuccess: (result: InfiniteData<PageResult>) => {
  console.log('==== Request 리스트 조회 성공 ====');
  console.log(result);
},
onError: (err: AxiosError) => {
  console.log(err.message);
},
keepPreviousData: true
})

 

 

 

pageParam

useInfiniteQuery를 사용하면 react query에서 정해놓은 pageParam이라는 prop을 받을 수 있다. 서버의 첫 페이지가 0이면 0으로 default 값을, 1이면 1로 설정해서 사용하면 된다. 첫 페이지 요청 때는 default 값으로 설정해놓은 값을 사용하고 그 다음 페이지부터는 계산해서 넣은 값을 사용하는데 바로 getNextPageParam option을 이용하면 된다.

 

getNextPageParam

getNextPageParam은 요청이 완료 된 후에 가장 최신 응답 값으로 몇 페이지인지 계산해서 return 하는 옵션이다. 서버마다 페이징 구현 방식이 다를테니 상황에 맞게 다음 페이지가 몇 페이지인지 return 하면 된다. 만약 현재 마지막 페이지일 경우에는 undefined를 return 하면 된다. Spring에서 제공해주는 페이징을 사용한다면 last 값을 이용해서 마지막페이지인지 확인해서 값을 return 해줄 수 있다. 

 

 

 

InfiniteData

 

보통 모바일 앱에서 infinite scroll list를 구현하면 새로운 페이지의 값을 받아서 기존의 리스트 뒤에 붙이는 식으로 구현한다. 그런데 useInfiniteQuery에서는 조금 다르게 구현해야한다.

InfiniteData는 useInfiniteQuery를 사용했을 때 받는 Response 구조이다. useInfiniteQuery는 Api 응답이 오면 마지막 페이지의 응답값만 들고 있는게 아니라 각 페이지마다의 응답값(pages)과 페이지 번호(pageParams)를 array에 저장하고 있다. 그렇기 때문에 모바일 앱 같이 스크롤로 리스트를 쓰는 경우에는 화면에 표현해 주기 위해서 1페이지부터 마지막 조회한 페이지까지의 리스트의 모든 응답 값을 합쳐서 보여줘야 한다.

 

화면 사용 예시 

useInfiniteQuery를 쓰면 페이지 마다의 값을 array에 저장하고 있기 때문에 실제 필요한 값을 꺼내주는 작업이 필요하다. Spring 에서 제공하는 페이징을 기준으로 구현한 내용인데 거의 모든 경우에 map을 사용해서 list 값만 꺼내면 [[list], [list], [list], [list]] 같은 구조가 된다. 그렇기 때문에 flat()을 사용해서 [...list, ...list, ...list] 구조로 바꿔준다. 

처리된 데이터 -> getRequestsQuery.data?.pages.map((page: PageResult) => page.content).flat()

<FlatList
  data={getRequestsQuery.data?.pages.map((page: PageResult) => page.content).flat()}
  onRefresh={onRefresh}
  refreshing={isFetching}
  ItemSeparatorComponent={() => <Divider style={{ height: 1, marginVertical: 20 }} />}
  keyExtractor={(item) => item.id}
  onEndReached={() => getRequestsQuery.fetchNextPage()}
  onEndReachedThreshold={1}
  renderItem={({ item }) => {
    return <RequestListItem item={item} />;
  }}
  ListEmptyComponent={() => <CommonNodata />}
  ListFooterComponent={() => <View style={{ height: 30}}/>}
/>

 

초기화

마지막으로 데이터들을 초기화하는 방법이다. useInfiniteQuery문을 그냥 refetch 하게되면 지금까지 조회한 모든 페이지를 다시 조회하게 된다. 그렇기 때문에 첫페이지만 다시 조회하기 위해서는 remove를 사용한 후에 refetch를 해야한다.

 

getRequestsQuery.remove();
getRequestsQuery.refetch().then();

 

만약 특정 페이지만 다시 조회 하고 싶다면 다음과 같이 사용하면 된다.

refetch({ refetchPage: (page, index) => index === 0 }) // index === {refetch하고 싶은 페이지}