rhanziy

React Native - 무한스크롤 useInfiniteQuery와 FlatList (typescript) 본문

React Native

React Native - 무한스크롤 useInfiniteQuery와 FlatList (typescript)

rhanziy 2023. 11. 20. 22:22

기존에 구현되어있는 상품리뷰에 점주들이 댓글을 작성할 수 있는 기능을 추가해야했다.

그러면서 storeId 에 딸린 리뷰 목록을 불러오는 페이지를 새로 만들면서 겪은 시행착오~

리액트 네이티브에서는 보통 무한스크롤 + 페이징으로 처리하는 것 같아 정리를 해놓고자 한다.

1. useInfiniteQuery

파라미터 값만 변경하여 동일한 useQuery를 무한정 호출할 때 사용하는 react query의 hook이다.

useQuery와의 차이점은

 

✅ 반환 객체에서 반환된 데이터 프로퍼티의 형태

useQuery에서의 데이터는 단순히 쿼리함수에서 반환된 데이터이고, useInfiniteQuery에서는 두개의 프로퍼티를 가지고온다.

하나는 데이터 페이지 객체의 Array 이고, 페이지 하나하나에 있는 요소가 useQuery를 통해 받아오는 데이터들이다. 그래서 infiniteQuery의 response data는 array 형식인가보다.(flatList를 써야하는 이유)

두번째는 각 페이지의 매개변수가 기록되는 pageParams이다. 모든 쿼리는 페이지 배열에 고유한 요소를 가지고 있고 그 요소로 페이지 데이터에  접근한다. 

 

✅ 구문

useQuery의 경우 state의 상태값을 통해 데이터에 접근했다면, useInfiniteQuery는 pageParam을 통해서 데이터 상태를 변화시킨다.

useInfiniteQuery('쿼리키', ({pageParam = defaultValue}) => fetchUrl(pageParam))

 

✅ useInfiniteQuery의 옵션

  • getNextPageParam
    • 다음 페이지로 가는 방식을 정의하는 함수
    • 마지막 또는 모든 페이지에 대한 데이터를 다룬다. 
    • 다음 api를 요청할 때 사용될 pageParam값을 정할 수 있다.
  • fetchNextPage
    • 사용자가 더 많은 데이터를 요청할 때 호출하는 함수
  • hasNextPage
    • getNextPageParam의 반환값을 기반으로 하는 함수
    • 마지막 쿼리의 데이터를 어떻게 사용할지 지시한다.
    • undefined의 경우 더 이상 데이터가 없다는 뜻
  • isFetchingNextPage
    • 다음 페이지를 가져오는건지, 일반적인 패칭인지를 구별

 

전체코드

// useReviews.ts 

const getReviews = async (pageNo: number) => {
  const {
    data: {data},
  } = await httpClient.get<Page<IReview>>('/owner/review', {
    params: {
      pageNo,
      pageSize: 10,
    },
  });

  const nextPage = data.totalPage > pageNo ? pageNo + 1 : undefined;

  return {...data, nextPage};
};


const useReviews = () => {
  const {data, hasNextPage, fetchNextPage, isFetched} = useInfiniteQuery(
    [queryKeys.reviews],
    ({pageParam = 1}) => getReviews(pageParam),
    {
      getNextPageParam: lastPage => lastPage.nextPage,
      keepPreviousData: true,
      refetchOnMount: false,
    },
  );

  const loadMore = useCallback(() => {
    if (hasNextPage) {
      fetchNextPage();
    }
  }, [hasNextPage, fetchNextPage]);

  return {data, loadMore, isFetched};
};

export default useReviews;



// index.ts 타입 정의 파일

export interface Page<T> {
  pageSize: number;
  totalCount: number;
  totalPage: number;
  items: T[];
}

 

 

참고사항

데이터를 그냥 refetch하게되면 지금까지 조회한 모든 페이지를 다시 조회하게 된다.! 특정페이지만 조회하는 방법은 이렇게

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

 

2. FlatList

InfiniteQuery 훅의 반환객체는 array이므로 FlatList를 사용해 화면에 보여주자. js map 함수역할과 비슷하고, scroll view보다 더 많은 기능을 내포하고 있는 list component이다. 스크롤 뷰와의 차이점은 화면에 보여지는 부분(혹은 설정한 수만큼의 데이터)만 렌더링 한다는 차이가 있슴. 사용법은 하나씩 보면 그냥 이해될 것임.

const ManageStoreReviewScreen: React.FC = () => {
  const {data, loadMore, isFetched} = useReviewsForOwner();

  const keyExtractor = useCallback(
    (review: IReview) => review.id.toString(),
    [],
  );

  const renderItem: ListRenderItem<IReview> = useCallback(
    ({item}) => (
      <View style={styles.item}>
        <ReviewItem {...item} productName={item.product.name} />
        {!item.children && <ReplyItem reviewId={item.id} />}
      </View>
    ),
    [],
  );

  return (
    <View>
      <View sentry-label="ManageStoreReviewScreen">    
        <FlatList
          data={data?.pages.map(page => page.items).flat()}
          keyExtractor={keyExtractor}
          renderItem={renderItem}
          ItemSeparatorComponent={Divider}
          onEndReached={loadMore}
          onEndReachedThreshold={0.7}
          ListEmptyComponent={
            isFetched ? (
              <>
                <View style={styles.empty}>
                  데이터 없을 때 보여줄 화면
                </View>
              </>
            ) : null
          }
        />
        <Divider />
      </View>
    </View>
  );
};

export default ManageStoreReviewScreen;
  • data - infiniteQuery 응답 데이터
  • keyExtractor - unique id 부여, 생략가능
  • renderItem - 각 항목별 렌더링할 아이템 로직 작성
  • onEndReached - 끝에 도달하면 실행할 함수 작성
  • onEndReachedThreshold={0.7} - onEndReach의 지점을 설정해주는 것. default는 1이며 1로 가까워질수록 FlatList 가장 하단, 0.5면 FlatList의 중간 지점에서 onEndReached 실행
  • ListEmptyComponent - 데이터가 없으면 보여줄 화면 렌더링
  • ItemSeparatorComponent - 을 통해 각 아이템 사이의 커스텀을 줄 수 있다.
  • ListHeaderComponent와 ListFooterComponent 활용해 header와 footer에 어떤 요소를 넣을지 커스텀/결정할 수 있다.
Comments