맨 처음 작업했던 후기 보여주는 사이트를 무한스크롤로 구현했는데 그 때 > useInfinityQuery와 Scroll event로 무한스크롤을 구현했었다.
다른 팀원분이 작업하신 캐러셀 부분애서 후기를 불러오고 페이지를 연동하였을 때 계속해서 오류가 나 오류를 해결하면서 스크롤 이벤트의 성능 저하를 함께 개선하기 위해
useInfinityQuery + interception Observer를 사용하여 리팩토링을 결정했다.
스크롤 이벤트를 계산할 때는 매번 스크롤 위치를 검사해줘야 해서 성능 이슈가 발생할 수 있다.
Intersection Observer API는 target 요소와 root 요소 사이의 교차 발생을 비동기적으로 관찰한다.
Dom과 target이 닿을 때 Callback이 실행되어 데이터를 불러오게 된다.
const {
fetchNextPage,
fetchPreviousPage,
hasNextPage,
hasPreviousPage,
isFetchingNextPage,
isFetchingPreviousPage,
...result
} = useInfiniteQuery({
queryKey,
queryFn: ({ pageParam = 1 }) => fetchPage(pageParam),
...options,
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})
export const QUERY_KEYS = {
guestBook: (invitationId: string) => ['guestbook', invitationId] as const,
invitation: () => ['invitation'] as const,
stickerImages: () => ['stickerImages'],
invitationCard: () => ['invitationCard'],
reviewCarousel: () => ['reviewCarousel'],
authUsers: () => ['authUsers'],
allImageReviews: () => ['allImageReviews'],
reviews: () => ['reviews'],
};
export const useReviewInfinite = () => {
const row = 10;
return useInfiniteQuery<Review[]>({
queryKey: QUERY_KEYS.reviews(),
queryFn: async ({ pageParam }) => await getReview({ pageParam: pageParam as number, row }),
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
return lastPage.length === row ? allPages.length * row : undefined;
},
});
};
const ReviewPage = () => {
const { data: reviewsData, isLoading, error, fetchNextPage, hasNextPage, isFetchingNextPage } = useReviewInfinite();
const observerRef = useRef<IntersectionObserver | null>(null);
const lastReviewRef = useCallback(
(node: HTMLDivElement | null) => {
if (isFetchingNextPage) return;
if (observerRef.current) observerRef.current.disconnect();
observerRef.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasNextPage) {
fetchNextPage();
}
});
if (node) observerRef.current.observe(node);
},
[isFetchingNextPage, fetchNextPage, hasNextPage],
);
if (isLoading) return <div>로딩 중...</div>;
if (error) return <div>에러가 발생했습니다: {error.message}. 다시 시도해 주세요.</div>;
const reviews = reviewsData?.pages.flatMap((page) => page) || [];
return (
<div className='flex flex-col w-full p-4'>
<h1>후기</h1>
<ReviewImage />
<div>
<ReviewCard reviews={reviews} />
{reviews.length > 0 && (
<div
ref={lastReviewRef}
className='h-1'
/>
)}
</div>
{isFetchingNextPage && <div>더 불러오는 중...</div>}
</div>
);
};
export default ReviewPage;
🔥 문제점
: 다른 팀원분이 작성하신 부분에서 계속 동일하게 오류가 나는 부분이 있었다. 홈화면의 후기 캐러셀 부분에서 후기 페이지로 넘어갈 때 계속해서 length 오류가 나는 것이었다. 어떤 부분에서 나는 지 오류를 해결하지 못하다가 queryKey를 분리하는 과정에서 두 컴포넌트에서 같은 queryKey를 사용하면서 오류를 발생시켰다는 것을 알게되었다.
✨ 해결방법
: 각자 다른 세분화된 queryKey를 부여한 이후 문제없이 정보를 잘 불러오게 되었다.
queryKey가 흩어져 있을 경우 관리가 쉽지않고 어디서 오류가 나는 지 찾기 쉽지 않은 것 같다. 그리고 명확한 이름과 변수명을 칭하는 것이 얼마나 중요한 지 한번 더 깨닫게 되었다.