summaryrefslogtreecommitdiff
path: root/hooks
diff options
context:
space:
mode:
Diffstat (limited to 'hooks')
-rw-r--r--hooks/use-data-table.ts91
1 files changed, 59 insertions, 32 deletions
diff --git a/hooks/use-data-table.ts b/hooks/use-data-table.ts
index f33c2e8b..d0521a7e 100644
--- a/hooks/use-data-table.ts
+++ b/hooks/use-data-table.ts
@@ -1,4 +1,4 @@
-// hooks/use-data-table.ts (업데이트)
+// hooks/use-data-table.ts (무한 렌더링 수정)
"use client"
import * as React from "react"
@@ -36,6 +36,7 @@ import useSWRInfinite from "swr/infinite"
import { getSortingStateParser } from "@/lib/parsers"
import { useDebouncedCallback } from "@/hooks/use-debounced-callback"
+import isEqual from "fast-deep-equal"
// 무한 스크롤 임계값 (이 값 이상이면 무한 스크롤 모드)
const INFINITE_SCROLL_THRESHOLD = 1_000_000
@@ -59,6 +60,7 @@ interface UseDataTableProps<TData>
| "onGroupingChange"
| "onExpandedChange"
| "getExpandedRowModel"
+ | "data"
>,
Required<Pick<TableOptions<TData>, "pageCount">> {
filterFields?: DataTableFilterField<TData>[]
@@ -150,7 +152,10 @@ export function useDataTable<TData>({
)
// pageSize 기반 무한 스크롤 모드 자동 결정
- const isInfiniteMode = perPage >= INFINITE_SCROLL_THRESHOLD
+ // const isInfiniteMode = perPage >= INFINITE_SCROLL_THRESHOLD
+
+ const isInfiniteMode = !!(infiniteScrollConfig && perPage >= INFINITE_SCROLL_THRESHOLD)
+
// -------- Sorting URL 동기화 --------
const [sorting, setSorting] = useQueryState(
@@ -211,7 +216,7 @@ export function useDataTable<TData>({
return Math.min(50, maxSize) // 기본 50개씩 로드
}, [isInfiniteMode, perPage, infiniteScrollConfig?.maxPageSize])
- // SWR 키 생성 함수
+ // SWR 키 생성 함수 - 안정화를 위해 useCallback 사용
const getKey = React.useCallback(
(pageIndex: number, previousPageData: InfiniteScrollResponse<TData> | null) => {
if (!isInfiniteMode || !infiniteScrollConfig) return null
@@ -273,12 +278,25 @@ export function useDataTable<TData>({
return swrData.flatMap(page => page.data)
}, [swrData, isInfiniteMode])
- // 무한 스크롤 메타 정보
+ // 무한 스크롤 메타 정보 - 안정화를 위해 useCallback 사용
+ const infiniteScrollActions = React.useMemo(() => ({
+ loadMore: () => {
+ if (swrData && swrData[swrData.length - 1]?.hasNextPage && !swrIsValidating) {
+ swrSetSize(prev => prev + 1)
+ }
+ },
+ reset: () => {
+ swrSetSize(1)
+ swrMutate()
+ },
+ refresh: () => swrMutate(),
+ }), [swrData, swrIsValidating, swrSetSize, swrMutate])
+
const infiniteMeta = React.useMemo(() => {
- if (!isInfiniteMode || !infiniteScrollConfig || !swrData) return null
+ if (!isInfiniteMode || !infiniteScrollConfig) return null
- const totalCount = swrData[0]?.total ?? null
- const hasNextPage = swrData[swrData.length - 1]?.hasNextPage ?? false
+ const totalCount = swrData?.[0]?.total ?? null
+ const hasNextPage = swrData?.[swrData.length - 1]?.hasNextPage ?? false
const isLoadingMore = swrIsValidating && swrData && typeof swrData[swrSize - 1] !== "undefined"
return {
@@ -286,28 +304,35 @@ export function useDataTable<TData>({
totalCount,
hasNextPage,
isLoadingMore,
- loadMore: () => {
- if (hasNextPage && !isLoadingMore) {
- swrSetSize(prev => prev + 1)
- }
- },
- reset: () => {
- swrSetSize(1)
- swrMutate()
- },
- refresh: () => swrMutate(),
+ onLoadMore: infiniteScrollActions.loadMore,
+ reset: infiniteScrollActions.reset,
+ refresh: infiniteScrollActions.refresh,
error: swrError,
isLoading: swrIsLoading,
isEmpty: swrData?.[0]?.data.length === 0,
}
- }, [isInfiniteMode, infiniteScrollConfig, swrData, swrIsValidating, swrSize, swrError, swrIsLoading, swrSetSize, swrMutate])
+ }, [
+ isInfiniteMode,
+ infiniteScrollConfig,
+ swrData,
+ swrIsValidating,
+ swrSize,
+ swrError,
+ swrIsLoading,
+ infiniteScrollActions
+ ])
+
+ // 검색어나 필터 변경 시 무한 스크롤 리셋 - infiniteMeta 의존성 제거
+ const resetInfiniteScroll = React.useCallback(() => {
+ if (isInfiniteMode && infiniteScrollActions) {
+ infiniteScrollActions.reset()
+ }
+ }, [isInfiniteMode, infiniteScrollActions])
- // 검색어나 필터 변경 시 무한 스크롤 리셋
+ // 필터 변경 시 리셋 - useEffect dependency 최소화
React.useEffect(() => {
- if (isInfiniteMode && infiniteMeta) {
- infiniteMeta.reset()
- }
- }, [search, parsedFilters, joinOperator, sortForSWR, isInfiniteMode])
+ resetInfiniteScroll()
+ }, [search, filters, joinOperator])
// 최종 데이터 결정
const finalData = isInfiniteMode ? infiniteData : initialData
@@ -325,11 +350,11 @@ export function useDataTable<TData>({
void setPage(1)
// 무한 스크롤에서 페이지네이션으로 전환 시 무한 스크롤 데이터 리셋
- if (wasInfiniteMode && infiniteMeta) {
- infiniteMeta.reset()
+ if (wasInfiniteMode && infiniteScrollActions) {
+ infiniteScrollActions.reset()
}
}
- }, [setPerPage, setPage, perPage, infiniteMeta])
+ }, [setPerPage, setPage, perPage, infiniteScrollActions])
// 그리고 onPaginationChange 함수도 수정
function onPaginationChange(updaterOrValue: Updater<PaginationState>) {
@@ -350,15 +375,17 @@ export function useDataTable<TData>({
}
}
}
+
// -------- 나머지 기존 로직들 --------
- function onSortingChange(updaterOrValue: Updater<SortingState>) {
- if (typeof updaterOrValue === "function") {
- const newSorting = updaterOrValue(sorting) as ExtendedSortingState<TData>
- void setSorting(newSorting)
- } else {
- void setSorting(updaterOrValue as ExtendedSortingState<TData>)
+ function onSortingChange(updater: Updater<SortingState>) {
+ const next =
+ typeof updater === "function" ? updater(sorting) : updater
+ if (!isEqual(next, sorting)) {
+ resetInfiniteScroll()
+ void setSorting(next as ExtendedSortingState<TData>)
}
}
+
function onGroupingChange(updaterOrValue: Updater<string[]>) {
if (typeof updaterOrValue === "function") {