일단 퍼블 단계에서는 Axios로 첫번째 페이지만 가져와서 뿌려준 상태였다. 또한 Query에서 상태관리도 안되구...
근데 알아보니까 React Query에 페이징에 특화된 훅이 있었다.
useInfiniteQuery...
https://tanstack.com/query/v4/docs/reference/useInfiniteQuery
일단 기본적인 반환값들은 useQuery와 겹치는게 많지만 추가로 위에 저 반환 값과 옵션들이 새로 생긴거라고 볼 수 있는데,
fetchNextPage, feetchPreviousPage : 다음, 이전 페이지를 적용해서 fetch를 해줌
hasNextPage, hasPrevious : 다음, 이전 페이지 존재 유뮤
isFetchingNextPage, isFetchingPreviousPage : 현재 페칭들이 진행중인지
getNextPageParam : 이 쿼리가 새 데이터를 받으면 마지막 페이지와 모든 페이지의 전체 배열을 모두 받는데, 단일 변수로 반 환을 해줘야합니다.
무한 스크롤이랑 페이지네이션에 써먹기에 정말 좋아보인다.. (츄릅)
내 코드에 일단 적용해 보았다. 일단 Axios 코드부터 손 봤다.
export const getProduct = async ({ pageParam = null }) => {
let response;
if (!pageParam) response = await axios.get('/product/');
else response = await axios.get(`/product/?cursor=${pageParam}`);
return response.data;
};
useInfiniteQuery 훅을 통해서 pageParam이 넘어 올건데 맨처음에 fetch할때는 안넘어올테니까 저렇게 null로 초기화를 해줬다. 그리고 우리 API는 저렇게 cursor에 cursor string을 쿼리로 넣어서 보내줘야지 다음 페이지를 보내준다. 그래서 저렇게 넣어줬다.
const { data,
fetchNextPage,
hasNextPage,
isFetching,
isFetchingNextPage
} = useInfiniteQuery(['ProductList'], getProduct, {
getNextPageParam: (lastPage, pages) => lastPage.cursor,
});
내가 useInfiniteQuery 코드다. 일단 당장 딱 필요한 반환값이랑 옵션만 넣었다. 이렇게 data를 반환받으면
이렇게 뱉어준다. 첫 페이지라서 pageParamssms 없고 pages안에 내가 원하는 값들이 들어있다.
그 다음에는 이렇게 뱉어준다. 데이터가 계속 쌓이는 걸 볼 수 있다. 버튼에 Button에 fetchNextPage를 달아서 동작을 시켜보자
잘 된다.. 이제 무한스크롤을 적용시켜봐야하는데... 역시 라이브러리보단 지원해주는 API를 쓰는게 맞지 않겠나
Intersection Observer API
https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API
여기서 나는 2번째 경우 라고할 수 있겠다. 어차피 맨아래 요소가 보이면 그냥 바로 fetchNextPage를 하면 되니까...
그래서 마지막 요소에 대해서 ref로 타겟해줘야한다.
// 타겟 엘리먼트
const observer = useRef<IntersectionObserver>();
{data?.pages.map((page, i) => (
<React.Fragment key={i}>
{page.results.map((result: any, i: number) => {
// 마지막 요소라면 ref를 넣어준다.
if (page.results.length === i + 1) {
return (
<Box key={result.id} ref={lastElementRef}>
<ProductCard product={result} onOpen={onOpen} />
</Box>
);
} else {
return (
<Box key={result.id}>
<ProductCard
key={result.id}
product={result}
onOpen={onOpen}
/>
</Box>
);
}
})}
</React.Fragment>
))}
그리고 lastElementRef가 관찰이 되면 Callback이 될 수 있게 Callback 함수를 아래와 같이 짜주었다.
const lastElementRef = useCallback(
(node) => {
//** 이미 fetch 중이라면 return*/
if (isLoading) return;
// 타겟 엘리먼트의 가시성 변화를 더 이상 감지하지 않는다.
if (observer.current) observer.current.disconnect();
// observer.current를 Intersection Observer 인스턴스를 생성
observer.current = new IntersectionObserver((entries) => {
// callback으로부터 받은 entries 배열에서 isIntersecting 노출 여부와
// 다음 페이지가 있는지 확인을 하면 다음 페이지 fetchNextPage
if (entries[0].isIntersecting && hasNextPage) {
fetchNextPage();
}
});
// 타겟 엘리먼트 다시 관찰 시작
// 기존 타겟 엘리먼트는 disconnect되었고,
// 새로 뿌려진 마지막 요소를 다시 타겟으로 삼게됨.
if (node) observer.current.observe(node);
},
[isLoading, hasNextPage, fetchNextPage],
);
여기서 Intersection Observer가 콜백되면 뱉어주는 entries라는 배열이 있는데 흥미로웠다.
여기서 나는 isIntersecting만 있으면 되니까 알고만 넘어갔다.
짠
'Project > 인코스런' 카테고리의 다른 글
토스 결제창 연동하기 (0) | 2022.10.19 |
---|---|
Unable to preventDefault inside passive event listener invocation - 에러 (1) | 2022.10.19 |
JWT 토큰 Refresh 하기 feat. Axios (0) | 2022.10.11 |
배송지 주소 검색 모달을 구현해보자.. (0) | 2022.10.07 |
시작부터... RTK로 햇으면 좋았을 것을.. (0) | 2022.10.05 |