코드 무한 스크롤 구현
This commit is contained in:
@@ -9,20 +9,21 @@ import type { CodeCategory } from "@/types/commonCode";
|
||||
*/
|
||||
export function useCategoriesInfinite(filters?: CategoryFilter) {
|
||||
return useInfiniteScroll<CodeCategory, CategoryFilter>({
|
||||
queryKey: queryKeys.categories.infinite(filters),
|
||||
queryKey: queryKeys.categories.infiniteList(filters),
|
||||
queryFn: async ({ pageParam, ...params }) => {
|
||||
// API 호출 시 페이지 정보 포함
|
||||
const expectedSize = pageParam === 1 ? 20 : 10; // 첫 페이지는 20개, 이후는 10개씩
|
||||
// 첫 페이지는 20개, 이후는 10개씩
|
||||
const pageSize = pageParam === 1 ? 20 : 10;
|
||||
const response = await commonCodeApi.categories.getList({
|
||||
...params,
|
||||
page: pageParam,
|
||||
size: expectedSize,
|
||||
size: pageSize,
|
||||
});
|
||||
|
||||
return {
|
||||
data: response.data || [],
|
||||
total: response.total,
|
||||
hasMore: (response.data?.length || 0) >= expectedSize, // 예상 크기와 같거나 크면 더 있을 수 있음
|
||||
currentPage: pageParam,
|
||||
pageSize: pageSize,
|
||||
};
|
||||
},
|
||||
initialPageParam: 1,
|
||||
@@ -31,9 +32,9 @@ export function useCategoriesInfinite(filters?: CategoryFilter) {
|
||||
staleTime: 5 * 60 * 1000, // 5분 캐싱
|
||||
// 커스텀 getNextPageParam 제공
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam) => {
|
||||
// 마지막 페이지의 데이터 개수가 요청한 크기보다 작으면 더 이상 페이지 없음
|
||||
const expectedSize = lastPageParam === 1 ? 20 : 10;
|
||||
if ((lastPage.data?.length || 0) < expectedSize) {
|
||||
// 마지막 페이지의 데이터 개수가 요청한 페이지 크기보다 작으면 더 이상 페이지 없음
|
||||
const currentPageSize = lastPage.pageSize || (lastPageParam === 1 ? 20 : 10);
|
||||
if ((lastPage.data?.length || 0) < currentPageSize) {
|
||||
return undefined;
|
||||
}
|
||||
return lastPageParam + 1;
|
||||
|
||||
@@ -25,9 +25,13 @@ export function useCreateCode() {
|
||||
mutationFn: ({ categoryCode, data }: { categoryCode: string; data: CreateCodeData }) =>
|
||||
commonCodeApi.codes.create(categoryCode, data),
|
||||
onSuccess: (_, variables) => {
|
||||
// 해당 카테고리의 모든 코드 쿼리 무효화
|
||||
// 해당 카테고리의 모든 코드 관련 쿼리 무효화 (일반 목록 + 무한 스크롤)
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.list(variables.categoryCode),
|
||||
queryKey: queryKeys.codes.all,
|
||||
});
|
||||
// 무한 스크롤 쿼리도 명시적으로 무효화
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -57,9 +61,13 @@ export function useUpdateCode() {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue),
|
||||
});
|
||||
// 해당 카테고리의 코드 목록 쿼리 무효화
|
||||
// 해당 카테고리의 모든 코드 관련 쿼리 무효화 (일반 목록 + 무한 스크롤)
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.list(variables.categoryCode),
|
||||
queryKey: queryKeys.codes.all,
|
||||
});
|
||||
// 무한 스크롤 쿼리도 명시적으로 무효화
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -80,7 +88,11 @@ export function useDeleteCode() {
|
||||
onSuccess: (_, variables) => {
|
||||
// 해당 코드 관련 쿼리 무효화 및 캐시 제거
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.list(variables.categoryCode),
|
||||
queryKey: queryKeys.codes.all,
|
||||
});
|
||||
// 무한 스크롤 쿼리도 명시적으로 무효화
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
|
||||
});
|
||||
queryClient.removeQueries({
|
||||
queryKey: queryKeys.codes.detail(variables.categoryCode, variables.codeValue),
|
||||
@@ -146,7 +158,11 @@ export function useReorderCodes() {
|
||||
onSettled: (_, __, variables) => {
|
||||
// 성공/실패와 관계없이 최종적으로 서버 데이터로 동기화
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.list(variables.categoryCode),
|
||||
queryKey: queryKeys.codes.all,
|
||||
});
|
||||
// 무한 스크롤 쿼리도 명시적으로 무효화
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.codes.infiniteList(variables.categoryCode),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
49
frontend/hooks/queries/useCodesInfinite.ts
Normal file
49
frontend/hooks/queries/useCodesInfinite.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { commonCodeApi } from "@/lib/api/commonCode";
|
||||
import { queryKeys } from "@/lib/queryKeys";
|
||||
import { useInfiniteScroll } from "@/hooks/useInfiniteScroll";
|
||||
import type { CodeFilter } from "@/lib/schemas/commonCode";
|
||||
import type { CodeInfo } from "@/types/commonCode";
|
||||
|
||||
/**
|
||||
* 코드 목록 무한 스크롤 훅
|
||||
* 카테고리별로 코드 목록을 무한 스크롤로 로드
|
||||
*/
|
||||
export function useCodesInfinite(categoryCode: string, filters?: CodeFilter) {
|
||||
return useInfiniteScroll<CodeInfo, CodeFilter>({
|
||||
queryKey: queryKeys.codes.infiniteList(categoryCode, filters),
|
||||
queryFn: async ({ pageParam, ...params }) => {
|
||||
// 첫 페이지는 20개, 이후는 10개씩
|
||||
const pageSize = pageParam === 1 ? 20 : 10;
|
||||
const response = await commonCodeApi.codes.getList(categoryCode, {
|
||||
...params,
|
||||
page: pageParam,
|
||||
size: pageSize,
|
||||
});
|
||||
|
||||
return {
|
||||
data: response.data || [],
|
||||
total: response.total,
|
||||
currentPage: pageParam,
|
||||
pageSize: pageSize,
|
||||
};
|
||||
},
|
||||
initialPageParam: 1,
|
||||
pageSize: 20, // 첫 페이지 기준
|
||||
params: filters,
|
||||
staleTime: 5 * 60 * 1000, // 5분 캐싱
|
||||
enabled: !!categoryCode, // categoryCode가 있을 때만 실행
|
||||
// 커스텀 getNextPageParam 제공
|
||||
getNextPageParam: (lastPage, allPages, lastPageParam) => {
|
||||
// 마지막 페이지의 데이터 개수가 요청한 페이지 크기보다 작으면 더 이상 페이지 없음
|
||||
const currentPageSize = lastPageParam === 1 ? 20 : 10;
|
||||
const dataLength = lastPage.data?.length || 0;
|
||||
|
||||
// 받은 데이터가 요청한 크기보다 작으면 마지막 페이지
|
||||
if (dataLength < currentPageSize) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return lastPageParam + 1;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -46,7 +46,25 @@ export function useInfiniteScroll<TData, TParams = Record<string, any>>({
|
||||
|
||||
// 모든 페이지의 데이터를 평탄화
|
||||
const flatData = useMemo(() => {
|
||||
return infiniteQuery.data?.pages.flatMap((page) => page.data) || [];
|
||||
if (!infiniteQuery.data?.pages) return [];
|
||||
|
||||
const allData = infiniteQuery.data.pages.flatMap((page) => page.data);
|
||||
|
||||
// 중복 제거 - code_value 또는 category_code를 기준으로
|
||||
const uniqueData = allData.filter((item, index, self) => {
|
||||
const key = (item as any).code_value || (item as any).category_code;
|
||||
if (!key) return true; // key가 없으면 그대로 유지
|
||||
|
||||
return (
|
||||
index ===
|
||||
self.findIndex((t) => {
|
||||
const tKey = (t as any).code_value || (t as any).category_code;
|
||||
return tKey === key;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return uniqueData;
|
||||
}, [infiniteQuery.data]);
|
||||
|
||||
// 총 개수 계산 (첫 번째 페이지의 total 사용)
|
||||
|
||||
Reference in New Issue
Block a user