@@ -11,8 +11,7 @@ import {
1111 getSortedRowModel ,
1212 useReactTable ,
1313} from '@tanstack/react-table' ;
14- import { useQueryStates } from 'nuqs' ;
15- import { useCallback , useEffect , useState } from 'react' ;
14+ import { useCallback , useEffect , useMemo , useState } from 'react' ;
1615import { useNavigation } from 'react-router-dom' ;
1716import { RemixFormProvider , useRemixForm } from 'remix-hook-form' ;
1817import { z } from 'zod' ;
@@ -21,8 +20,9 @@ import { DataTablePagination } from '../ui/data-table/data-table-pagination';
2120import { Table , TableBody , TableCell , TableHead , TableHeader , TableRow } from '../ui/table' ;
2221import { DataTableRouterToolbar , type DataTableRouterToolbarProps } from './data-table-router-toolbar' ;
2322
24- // Import the nuqs parsers and the inferred type
25- import { type DataTableRouterState , type FilterValue , dataTableRouterParsers } from './data-table-router-parsers' ;
23+ // Import the parsers and the inferred type
24+ import type { DataTableRouterState , FilterValue } from './data-table-router-parsers' ;
25+ import { getDefaultDataTableState , useDataTableUrlState } from './use-data-table-url-state' ;
2626
2727// Schema for form data validation and type safety
2828const dataTableSchema = z . object ( {
@@ -56,23 +56,13 @@ export function DataTableRouterForm<TData, TValue>({
5656 const navigation = useNavigation ( ) ;
5757 const isLoading = navigation . state === 'loading' ;
5858
59- // --- nuqs state management ---
60- // Use nuqs to manage URL state. Debounce options can be set here per parser if needed.
61- const [ urlState , setUrlState ] = useQueryStates ( dataTableRouterParsers , {
62- // Default nuqs options (shallow routing, replace history, no scroll)
63- history : 'replace' , // Default
64- shallow : false , // we want to re-run the loader when the url changes
65- // scroll: false, // Default
66- // Configure debounce globally if needed (though nuqs batches by default)
67- // throttleMs: 300,
68- } ) ;
69- // --- End nuqs state management ---
59+ // Use our custom hook for URL state management
60+ const { urlState, setUrlState } = useDataTableUrlState ( ) ;
7061
71- // Initialize RHF to *reflect* the nuqs state
62+ // Initialize RHF to *reflect* the URL state
7263 const methods = useRemixForm < DataTableRouterState > ( {
73- // Use the nuqs inferred type
7464 // No resolver needed if Zod isn't primary validation driver here
75- defaultValues : urlState , // Initialize with current URL state from nuqs
65+ defaultValues : urlState , // Initialize with current URL state
7666 } ) ;
7767
7868 // Sync RHF state if urlState changes (e.g., back/forward, external link)
@@ -87,7 +77,7 @@ export function DataTableRouterForm<TData, TValue>({
8777 const [ columnVisibility , setColumnVisibility ] = useState < VisibilityState > ( { } ) ;
8878 const [ rowSelection , setRowSelection ] = useState ( { } ) ;
8979
90- // Table instance uses RHF state (which mirrors nuqs/ URL state)
80+ // Table instance uses RHF state (which mirrors URL state)
9181 const table = useReactTable ( {
9282 data,
9383 columns,
@@ -132,24 +122,25 @@ export function DataTableRouterForm<TData, TValue>({
132122 } ,
133123 } ) ;
134124
135- // Pagination handler updates nuqs state
125+ // Determine default pageSize and visible columns for skeleton loader
126+ const defaultDataTableState = getDefaultDataTableState ( defaultStateValues ) ;
127+ const visibleColumns = table . getVisibleFlatColumns ( ) ;
128+ // Generate stable IDs for skeleton rows based on current pageSize or fallback
129+ const skeletonRowIds = useMemo ( ( ) => {
130+ const count = urlState . pageSize > 0 ? urlState . pageSize : defaultDataTableState . pageSize ;
131+ return Array . from ( { length : count } , ( ) => window . crypto . randomUUID ( ) ) ;
132+ } , [ urlState . pageSize , defaultDataTableState . pageSize ] ) ;
133+
134+ // Pagination handler updates URL state
136135 const handlePaginationChange = useCallback (
137136 ( pageIndex : number , newPageSize : number ) => {
138137 setUrlState ( { page : pageIndex , pageSize : newPageSize } ) ;
139138 } ,
140139 [ setUrlState ] ,
141140 ) ;
142141
143- // Derive default values directly from parsers for reset
144- const standardStateValues : DataTableRouterState = {
145- search : '' ,
146- filters : [ ] ,
147- page : 0 ,
148- pageSize : 10 ,
149- sortField : '' ,
150- sortOrder : 'asc' ,
151- ...defaultStateValues ,
152- } ;
142+ // Get default state values using our utility function
143+ const standardStateValues = getDefaultDataTableState ( defaultStateValues ) ;
153144
154145 // Handle pagination props separately
155146 const paginationProps = {
@@ -184,14 +175,19 @@ export function DataTableRouterForm<TData, TValue>({
184175 </ TableHeader >
185176 < TableBody >
186177 { isLoading ? (
187- < TableRow >
188- < TableCell colSpan = { columns . length } className = "h-24 text-center" >
189- Loading...
190- </ TableCell >
191- </ TableRow >
178+ // Skeleton rows matching pageSize with zebra background
179+ skeletonRowIds . map ( ( rowId ) => (
180+ < TableRow key = { rowId } className = "even:bg-gray-50" >
181+ { visibleColumns . map ( ( column ) => (
182+ < TableCell key = { column . id } className = "py-2" >
183+ < div className = "h-6 my-1.5 bg-gray-200 rounded animate-pulse w-full" />
184+ </ TableCell >
185+ ) ) }
186+ </ TableRow >
187+ ) )
192188 ) : table . getRowModel ( ) . rows ?. length ? (
193189 table . getRowModel ( ) . rows . map ( ( row ) => (
194- < TableRow key = { row . id } data-state = { row . getIsSelected ( ) && 'selected' } >
190+ < TableRow key = { row . id } data-state = { row . getIsSelected ( ) && 'selected' } className = "even:bg-gray-50" >
195191 { row . getVisibleCells ( ) . map ( ( cell ) => (
196192 < TableCell key = { cell . id } > { flexRender ( cell . column . columnDef . cell , cell . getContext ( ) ) } </ TableCell >
197193 ) ) }
0 commit comments