Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | import { useMemo } from 'react'; import { InfiniteData, QueryObserverResult, useSuspenseInfiniteQuery } from '@tanstack/react-query'; const STALE_TIME = 3 * 1000; // 3초 const DEFAULT_ERROR_MESSAGE = '데이터를 불러오는데 실패했습니다.'; // 범용 무한 스크롤 응답 타입 (다른 페이지에서도 사용 가능) export interface InfiniteScrollResponse<T> { items: T[]; nextCursor: number | null; } // 범용 무한 스크롤 파라미터 타입 export interface UseInfiniteScrollParams< TItem, TQueryKey extends readonly unknown[] = readonly unknown[], > { queryFn: (params: { cursor?: number; keyword?: string; size: number; }) => Promise<InfiniteScrollResponse<TItem>>; queryKey: TQueryKey; keyword?: string; pageSize?: number; staleTime?: number; errorMessage?: string; // 모든 데이터 로드 완료 메시지 (선택, 기본값: "모든 데이터를 불러왔습니다.") completedMessage?: string; enabled?: boolean; } // 범용 무한 스크롤 반환 타입 export interface UseInfiniteScrollReturn<TItem> { items: TItem[]; nextCursor: number | null; error: Error | null; fetchNextPage: () => void; hasNextPage: boolean; isFetchingNextPage: boolean; isFetching: boolean; isLoading: boolean; refetch: () => Promise< QueryObserverResult<InfiniteData<InfiniteScrollResponse<TItem>, number | undefined>, Error> >; // 모든 데이터 로드 완료시 메시지 completedMessage: string; } // eslint-disable-next-line func-style export function useInfiniteScroll< TItem, TQueryKey extends readonly unknown[] = readonly unknown[], >({ queryFn, queryKey, keyword, pageSize = 10, staleTime = STALE_TIME, errorMessage = DEFAULT_ERROR_MESSAGE, // enabled = true, completedMessage = '모든 데이터를 불러왔습니다.', }: UseInfiniteScrollParams<TItem, TQueryKey>): UseInfiniteScrollReturn<TItem> { type InfiniteScrollData = InfiniteData<InfiniteScrollResponse<TItem>, number | undefined>; const { data, error, fetchNextPage, hasNextPage, isFetchingNextPage, isFetching, isLoading, refetch, } = useSuspenseInfiniteQuery< InfiniteScrollResponse<TItem>, Error, InfiniteScrollData, TQueryKey, number | undefined >({ queryKey, // enabled, queryFn: async ({ pageParam }) => { const response = await queryFn({ cursor: pageParam, keyword, size: pageSize, }); return response; }, initialPageParam: undefined, getNextPageParam: (lastPage) => { return lastPage.nextCursor ?? undefined; }, staleTime, }); // 여러 페이지의 아이템을 하나의 배열로 합치기 const items = useMemo(() => { if (!data?.pages) return []; return data.pages.flatMap((page) => page.items); }, [data]); // 마지막 페이지의 nextCursor 값 const nextCursor = useMemo(() => { if (!data?.pages || data.pages.length === 0) return null; const lastPage = data.pages[data.pages.length - 1]; return lastPage?.nextCursor ?? null; }, [data]); // 에러 객체 변환 const errorObject = useMemo(() => { if (!error) return null; if (error instanceof Error) return error; return new Error(errorMessage); }, [error, errorMessage]); return { items, nextCursor, error: errorObject, fetchNextPage, hasNextPage: hasNextPage ?? false, isFetchingNextPage, isFetching, isLoading, refetch, completedMessage, }; } |