@@ -10,7 +10,7 @@ import {
1010 type TableOptions ,
1111 useReactTable ,
1212} from '@tanstack/react-table'
13- import { useEffect , useMemo , useRef } from 'react'
13+ import { useCallback , useEffect , useMemo , useRef , useState } from 'react'
1414import { useLocalStorage } from 'usehooks-ts'
1515import type {
1616 DefaultTemplate ,
@@ -38,8 +38,6 @@ import { fallbackData, templatesTableConfig, useColumns } from './table-config'
3838
3939const PAGE_SIZE = 50
4040
41- // Maps a table column id to its server sort-token base. Only mapped columns are
42- // sortable server-side; anything else falls back to updated_at.
4341const COLUMN_TO_SORT_BASE : Record < string , string > = {
4442 name : 'name' ,
4543 cpuCount : 'cpu_count' ,
@@ -71,6 +69,7 @@ export default function TemplatesTable() {
7169 fetchNextPage,
7270 hasNextPage,
7371 isFetchingNextPage,
72+ isFetching,
7473 } = useSuspenseInfiniteQuery (
7574 trpc . templates . getTemplates . infiniteQueryOptions (
7675 {
@@ -79,7 +78,6 @@ export default function TemplatesTable() {
7978 cpuCount,
8079 memoryMB,
8180 public : isPublic ,
82- // The search input already debounces before writing to the store.
8381 search : globalFilter || undefined ,
8482 sort,
8583 } ,
@@ -96,6 +94,22 @@ export default function TemplatesTable() {
9694 [ data ]
9795 )
9896
97+ const { isRefetching, clearRefetching } = useTemplatesRefetchTracking (
98+ sort ,
99+ globalFilter ,
100+ cpuCount ,
101+ memoryMB ,
102+ isPublic
103+ )
104+
105+ useEffect ( ( ) => {
106+ if ( ! isFetching && isRefetching ) {
107+ clearRefetching ( )
108+ }
109+ } , [ isFetching , isRefetching , clearRefetching ] )
110+
111+ const isListDimmed = isRefetching && templates . length > 0
112+
99113 const scrollRef = useRef < HTMLDivElement > ( null )
100114
101115 const [ columnSizing , setColumnSizing ] = useLocalStorage < ColumnSizingState > (
@@ -125,9 +139,6 @@ export default function TemplatesTable() {
125139
126140 const columnSizeVars = useColumnSizeVars ( table )
127141
128- // The query refetches from the first page whenever sort/filter/search change,
129- // so reset the scroll position to the top to match. These values are
130- // intentional triggers even though the effect body only touches the ref.
131142 // biome-ignore lint/correctness/useExhaustiveDependencies: scroll reset is triggered by query-input changes
132143 useEffect ( ( ) => {
133144 if ( scrollRef . current ) {
@@ -207,9 +218,34 @@ export default function TemplatesTable() {
207218 hasNextPage = { hasNextPage }
208219 isFetchingNextPage = { isFetchingNextPage }
209220 fetchNextPage = { fetchNextPage }
221+ isRefetching = { isListDimmed }
210222 />
211223 </ DataTable >
212224 </ div >
213225 </ ClientOnly >
214226 )
215227}
228+
229+ function useTemplatesRefetchTracking (
230+ sort : string ,
231+ globalFilter : string ,
232+ cpuCount : number | undefined ,
233+ memoryMB : number | undefined ,
234+ isPublic : boolean | undefined
235+ ) {
236+ const [ isRefetching , setIsRefetching ] = useState ( false )
237+ const isFirstRender = useRef ( true )
238+
239+ // biome-ignore lint/correctness/useExhaustiveDependencies: these are change triggers, not values read in the effect
240+ useEffect ( ( ) => {
241+ if ( isFirstRender . current ) {
242+ isFirstRender . current = false
243+ return
244+ }
245+ setIsRefetching ( true )
246+ } , [ sort , globalFilter , cpuCount , memoryMB , isPublic ] )
247+
248+ const clearRefetching = useCallback ( ( ) => setIsRefetching ( false ) , [ ] )
249+
250+ return { isRefetching, clearRefetching }
251+ }
0 commit comments