Skip to content

Commit 0bf634e

Browse files
committed
Refactor data table components to improve loading state and pagination handling. Added skeleton row IDs for loading state and optimized filter logic in the toolbar. Updated imports for better type usage.
1 parent 76b4578 commit 0bf634e

3 files changed

Lines changed: 43 additions & 35 deletions

File tree

apps/docs/src/remix-hook-form/data-table-router-form.stories.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const columns: ColumnDef<User>[] = [
8787
// Component to display the data table with router form integration
8888
function DataTableRouterFormExample() {
8989
const loaderData = useLoaderData<DataResponse>();
90-
90+
9191
// Ensure we have data even if loaderData is undefined
9292
const data = loaderData?.data ?? [];
9393
const pageCount = loaderData?.meta.pageCount ?? 0;
@@ -142,8 +142,8 @@ function DataTableRouterFormExample() {
142142
// Loader function to handle data fetching based on URL parameters
143143
const handleDataFetch = async ({ request }: LoaderFunctionArgs) => {
144144
// Add a small delay to simulate network latency
145-
await new Promise(resolve => setTimeout(resolve, 300));
146-
145+
await new Promise((resolve) => setTimeout(resolve, 300));
146+
147147
// Ensure we have a valid URL object
148148
const url = request?.url ? new URL(request.url) : new URL('http://localhost?page=0&pageSize=10');
149149
const params = url.searchParams;
@@ -200,9 +200,9 @@ const handleDataFetch = async ({ request }: LoaderFunctionArgs) => {
200200
}
201201

202202
// 4. Apply pagination
203-
// Provide defaults again for TS, although parsers guarantee numbers
204-
const safePage = page ?? 0;
205-
const safePageSize = pageSize ?? 10;
203+
// Determine safe values for page and pageSize using defaultValue when params are missing
204+
const safePage = params.has('page') ? page : dataTableRouterParsers.page.defaultValue;
205+
const safePageSize = params.has('pageSize') ? pageSize : dataTableRouterParsers.pageSize.defaultValue;
206206
const start = safePage * safePageSize;
207207
const paginatedData = filteredData.slice(start, start + safePageSize);
208208

packages/components/src/remix-hook-form/data-table-router-form.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
getSortedRowModel,
1212
useReactTable,
1313
} from '@tanstack/react-table';
14-
import { useCallback, useEffect, useState } from 'react';
14+
import { useCallback, useEffect, useMemo, useState } from 'react';
1515
import { useNavigation } from 'react-router-dom';
1616
import { RemixFormProvider, useRemixForm } from 'remix-hook-form';
1717
import { z } from 'zod';
@@ -21,7 +21,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '.
2121
import { DataTableRouterToolbar, type DataTableRouterToolbarProps } from './data-table-router-toolbar';
2222

2323
// Import the parsers and the inferred type
24-
import { type DataTableRouterState, type FilterValue } from './data-table-router-parsers';
24+
import type { DataTableRouterState, FilterValue } from './data-table-router-parsers';
2525
import { getDefaultDataTableState, useDataTableUrlState } from './use-data-table-url-state';
2626

2727
// Schema for form data validation and type safety
@@ -122,6 +122,15 @@ export function DataTableRouterForm<TData, TValue>({
122122
},
123123
});
124124

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+
125134
// Pagination handler updates URL state
126135
const handlePaginationChange = useCallback(
127136
(pageIndex: number, newPageSize: number) => {
@@ -166,14 +175,19 @@ export function DataTableRouterForm<TData, TValue>({
166175
</TableHeader>
167176
<TableBody>
168177
{isLoading ? (
169-
<TableRow>
170-
<TableCell colSpan={columns.length} className="h-24 text-center">
171-
Loading...
172-
</TableCell>
173-
</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+
))
174188
) : table.getRowModel().rows?.length ? (
175189
table.getRowModel().rows.map((row) => (
176-
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
190+
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'} className="even:bg-gray-50">
177191
{row.getVisibleCells().map((cell) => (
178192
<TableCell key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</TableCell>
179193
))}

packages/components/src/remix-hook-form/data-table-router-toolbar.tsx

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { Cross2Icon, MixerHorizontalIcon, PlusIcon } from '@radix-ui/react-icons';
2-
import { type Table } from '@tanstack/react-table';
1+
import { Cross2Icon } from '@radix-ui/react-icons';
2+
import type { Table } from '@tanstack/react-table';
33
import { type ChangeEvent, useCallback } from 'react';
44
import { useRemixFormContext } from 'remix-hook-form';
55

66
import { Button } from '../ui/button';
7-
import { TextField } from './text-field';
87
import { DataTableFacetedFilter } from '../ui/data-table/data-table-faceted-filter';
98
import { DataTableViewOptions } from '../ui/data-table/data-table-view-options';
10-
import { type DataTableRouterState, type FilterValue } from './data-table-router-parsers';
9+
import type { DataTableRouterState, FilterValue } from './data-table-router-parsers';
10+
import { TextField } from './text-field';
1111

1212
export interface DataTableFilterOption {
1313
label: string;
@@ -56,23 +56,17 @@ export function DataTableRouterToolbar<TData>({
5656
(columnId: string, value: string[]) => {
5757
const currentFilters = [...watchedFilters];
5858
const existingFilterIndex = currentFilters.findIndex((filter: FilterValue) => filter.id === columnId);
59-
let newFilters;
60-
61-
if (value.length === 0) {
62-
// Remove filter if no values selected
63-
if (existingFilterIndex !== -1) {
64-
newFilters = currentFilters.filter((_, index) => index !== existingFilterIndex);
65-
} else {
66-
newFilters = currentFilters;
67-
}
59+
let newFilters: FilterValue[];
60+
61+
if (value.length === 0 && existingFilterIndex !== -1) {
62+
newFilters = currentFilters.filter((_, i) => i !== existingFilterIndex);
63+
} else if (value.length === 0) {
64+
newFilters = currentFilters;
65+
} else if (existingFilterIndex !== -1) {
66+
newFilters = [...currentFilters];
67+
newFilters[existingFilterIndex] = { id: columnId, value };
6868
} else {
69-
// Add or update filter
70-
if (existingFilterIndex !== -1) {
71-
newFilters = [...currentFilters];
72-
newFilters[existingFilterIndex] = { id: columnId, value };
73-
} else {
74-
newFilters = [...currentFilters, { id: columnId, value }];
75-
}
69+
newFilters = [...currentFilters, { id: columnId, value }];
7670
}
7771
setUrlState({ filters: newFilters, page: 0 });
7872
},
@@ -100,7 +94,7 @@ export function DataTableRouterToolbar<TData>({
10094
placeholder={`Search ${searchableColumns.map((column) => column.title).join(', ')}...`}
10195
value={watchedSearch}
10296
onChange={handleSearchChange}
103-
className="h-8 w-full"
97+
className="w-full"
10498
suffix={
10599
watchedSearch ? (
106100
<Button

0 commit comments

Comments
 (0)