0
# Tables & Lists
1
2
Comprehensive table functionality with sorting, filtering, pagination, search, and URL synchronization for data-heavy interfaces.
3
4
## Capabilities
5
6
### Core Table Management
7
8
#### useTable Hook
9
10
Provides comprehensive table functionality including sorting, filtering, pagination, and URL synchronization for data-intensive applications.
11
12
```typescript { .api }
13
/**
14
* Provides comprehensive table functionality with data management
15
* @param params - Table configuration options
16
* @returns Table state, data, and control functions
17
*/
18
function useTable<TQueryFnData = BaseRecord, TError = HttpError, TData = TQueryFnData>(
19
params?: UseTableConfig<TQueryFnData, TError, TData>
20
): UseTableReturnType<TData, TError>;
21
22
interface UseTableConfig<TQueryFnData, TError, TData> {
23
/** Resource name - inferred from route if not provided */
24
resource?: string;
25
/** Pagination configuration */
26
pagination?: {
27
/** Initial current page */
28
current?: number;
29
/** Initial page size */
30
pageSize?: number;
31
/** Pagination mode - server, client, or disabled */
32
mode?: "server" | "client" | "off";
33
};
34
/** Sorting configuration */
35
sorters?: {
36
/** Initial sort configuration */
37
initial?: CrudSorting;
38
/** Permanent sorts that cannot be changed */
39
permanent?: CrudSorting;
40
/** Sorting mode - server or disabled */
41
mode?: "server" | "off";
42
};
43
/** Filtering configuration */
44
filters?: {
45
/** Initial filter configuration */
46
initial?: CrudFilters;
47
/** Permanent filters that cannot be changed */
48
permanent?: CrudFilters;
49
/** How to handle new filters with existing ones */
50
defaultBehavior?: "merge" | "replace";
51
/** Filtering mode - server or disabled */
52
mode?: "server" | "off";
53
};
54
/** Whether to sync table state with URL */
55
syncWithLocation?: boolean;
56
/** Additional metadata */
57
meta?: MetaQuery;
58
/** Data provider name */
59
dataProviderName?: string;
60
/** Success notification configuration */
61
successNotification?: SuccessErrorNotification | false;
62
/** Error notification configuration */
63
errorNotification?: SuccessErrorNotification | false;
64
/** Live mode configuration */
65
liveMode?: LiveModeProps;
66
/** Callback for live events */
67
onLiveEvent?: (event: LiveEvent) => void;
68
/** Query options */
69
queryOptions?: UseQueryOptions<GetListResponse<TQueryFnData>, TError>;
70
/** Override mutation mode */
71
mutationMode?: MutationMode;
72
/** Timeout for undoable operations */
73
undoableTimeout?: number;
74
/** Cache invalidation configuration */
75
invalidates?: Array<string>;
76
}
77
78
interface UseTableReturnType<TData, TError> {
79
/** React Query result for table data */
80
tableQuery: UseQueryResult<GetListResponse<TData>, TError>;
81
/** Current sorting configuration */
82
sorters: CrudSorting;
83
/** Function to update sorting */
84
setSorters: (sorters: CrudSorting) => void;
85
/** Current filtering configuration */
86
filters: CrudFilters;
87
/** Function to update filters */
88
setFilters: (filters: CrudFilters, behavior?: "merge" | "replace") => void;
89
/** Current page number */
90
current: number;
91
/** Function to set current page */
92
setCurrent: (page: number) => void;
93
/** Current page size */
94
pageSize: number;
95
/** Function to set page size */
96
setPageSize: (size: number) => void;
97
/** Total page count */
98
pageCount: number;
99
/** Create link function for external sync */
100
createLinkForSyncWithLocation: (params: SyncWithLocationParams) => string;
101
/** Processed table result */
102
result: {
103
data: TData[];
104
total: number;
105
};
106
/** Loading overtime information */
107
overtime: UseLoadingOvertimeReturnType;
108
}
109
110
interface SyncWithLocationParams {
111
/** Pagination parameters */
112
pagination?: {
113
current?: number;
114
pageSize?: number;
115
};
116
/** Sorting parameters */
117
sorters?: CrudSorting;
118
/** Filtering parameters */
119
filters?: CrudFilters;
120
}
121
```
122
123
**Usage Example:**
124
125
```typescript
126
import { useTable } from "@refinedev/core";
127
import { useState } from "react";
128
129
interface Post {
130
id: number;
131
title: string;
132
content: string;
133
status: "draft" | "published";
134
createdAt: string;
135
author: {
136
name: string;
137
};
138
}
139
140
function PostsTable() {
141
const {
142
tableQuery,
143
sorters,
144
setSorters,
145
filters,
146
setFilters,
147
current,
148
setCurrent,
149
pageSize,
150
setPageSize,
151
pageCount
152
} = useTable<Post>({
153
resource: "posts",
154
pagination: {
155
current: 1,
156
pageSize: 10,
157
mode: "server"
158
},
159
sorters: {
160
initial: [{
161
field: "createdAt",
162
order: "desc"
163
}],
164
mode: "server"
165
},
166
filters: {
167
initial: [{
168
field: "status",
169
operator: "eq",
170
value: "published"
171
}],
172
mode: "server"
173
},
174
syncWithLocation: true
175
});
176
177
const { data, isLoading, error } = tableQuery;
178
179
const handleSort = (field: string) => {
180
const existingSorter = sorters.find(s => s.field === field);
181
if (existingSorter) {
182
const newOrder = existingSorter.order === "asc" ? "desc" : "asc";
183
setSorters([{ field, order: newOrder }]);
184
} else {
185
setSorters([{ field, order: "asc" }]);
186
}
187
};
188
189
const handleFilter = (field: string, value: string) => {
190
setFilters([{
191
field,
192
operator: "contains",
193
value
194
}], "merge");
195
};
196
197
if (isLoading) return <div>Loading...</div>;
198
if (error) return <div>Error: {error.message}</div>;
199
200
return (
201
<div>
202
{/* Search filters */}
203
<div className="filters">
204
<input
205
placeholder="Search by title..."
206
onChange={(e) => handleFilter("title", e.target.value)}
207
/>
208
<select onChange={(e) => handleFilter("status", e.target.value)}>
209
<option value="">All Status</option>
210
<option value="draft">Draft</option>
211
<option value="published">Published</option>
212
</select>
213
</div>
214
215
{/* Table */}
216
<table>
217
<thead>
218
<tr>
219
<th onClick={() => handleSort("title")}>
220
Title {getSortIcon("title", sorters)}
221
</th>
222
<th onClick={() => handleSort("status")}>
223
Status {getSortIcon("status", sorters)}
224
</th>
225
<th onClick={() => handleSort("createdAt")}>
226
Created {getSortIcon("createdAt", sorters)}
227
</th>
228
<th>Author</th>
229
</tr>
230
</thead>
231
<tbody>
232
{data?.data.map(post => (
233
<tr key={post.id}>
234
<td>{post.title}</td>
235
<td>{post.status}</td>
236
<td>{new Date(post.createdAt).toLocaleDateString()}</td>
237
<td>{post.author.name}</td>
238
</tr>
239
))}
240
</tbody>
241
</table>
242
243
{/* Pagination */}
244
<div className="pagination">
245
<button
246
onClick={() => setCurrent(current - 1)}
247
disabled={current === 1}
248
>
249
Previous
250
</button>
251
252
<span>
253
Page {current} of {pageCount} (Total: {data?.total})
254
</span>
255
256
<button
257
onClick={() => setCurrent(current + 1)}
258
disabled={current === pageCount}
259
>
260
Next
261
</button>
262
263
<select
264
value={pageSize}
265
onChange={(e) => setPageSize(parseInt(e.target.value))}
266
>
267
<option value={10}>10 per page</option>
268
<option value={20}>20 per page</option>
269
<option value={50}>50 per page</option>
270
</select>
271
</div>
272
</div>
273
);
274
}
275
276
function getSortIcon(field: string, sorters: CrudSorting) {
277
const sorter = sorters.find(s => s.field === field);
278
if (!sorter) return "↕️";
279
return sorter.order === "asc" ? "↑" : "↓";
280
}
281
```
282
283
### Select & Dropdown Management
284
285
#### useSelect Hook
286
287
Specialized hook for select/dropdown components with search, pagination, and option management.
288
289
```typescript { .api }
290
/**
291
* Manages select/dropdown components with search and pagination
292
* @param params - Select configuration options
293
* @returns Select state, options, and control functions
294
*/
295
function useSelect<TQueryFnData = BaseRecord, TError = HttpError, TData = TQueryFnData>(
296
params: UseSelectConfig<TQueryFnData, TError, TData>
297
): UseSelectReturnType<TData, TError>;
298
299
interface UseSelectConfig<TQueryFnData, TError, TData> {
300
/** Resource name for fetching options */
301
resource: string;
302
/** Field to use as option label */
303
optionLabel?: keyof TData | string;
304
/** Field to use as option value */
305
optionValue?: keyof TData | string;
306
/** Field to search in */
307
searchField?: string;
308
/** Additional filters */
309
filters?: CrudFilters;
310
/** Sorting configuration */
311
sorters?: CrudSorting;
312
/** Default selected value(s) */
313
defaultValue?: BaseKey | BaseKey[];
314
/** Search debounce delay in milliseconds */
315
debounce?: number;
316
/** Query options */
317
queryOptions?: UseQueryOptions<GetListResponse<TQueryFnData>, TError>;
318
/** Pagination configuration */
319
pagination?: {
320
current?: number;
321
pageSize?: number;
322
mode?: "server" | "client" | "off";
323
};
324
/** Additional metadata */
325
meta?: MetaQuery;
326
/** Data provider name */
327
dataProviderName?: string;
328
/** Success notification configuration */
329
successNotification?: SuccessErrorNotification | false;
330
/** Error notification configuration */
331
errorNotification?: SuccessErrorNotification | false;
332
/** Live mode configuration */
333
liveMode?: LiveModeProps;
334
/** Callback for live events */
335
onLiveEvent?: (event: LiveEvent) => void;
336
}
337
338
interface UseSelectReturnType<TData, TError> {
339
/** React Query result */
340
query: UseQueryResult<GetListResponse<TData>, TError>;
341
/** Query result (alias) */
342
queryResult: UseQueryResult<GetListResponse<TData>, TError>;
343
/** Formatted options for select component */
344
options: SelectOption[];
345
/** Search handler function */
346
onSearch: (value: string) => void;
347
/** Current search value */
348
search: string;
349
}
350
351
interface SelectOption {
352
/** Option label for display */
353
label: string;
354
/** Option value */
355
value: BaseKey;
356
/** Raw data object */
357
data?: BaseRecord;
358
}
359
```
360
361
**Usage Example:**
362
363
```typescript
364
import { useSelect } from "@refinedev/core";
365
import { useState } from "react";
366
367
interface Category {
368
id: number;
369
name: string;
370
description?: string;
371
}
372
373
function CategorySelect({ value, onChange }: { value?: number; onChange: (value: number) => void }) {
374
const { options, onSearch, query } = useSelect<Category>({
375
resource: "categories",
376
optionLabel: "name",
377
optionValue: "id",
378
searchField: "name",
379
sorters: [{
380
field: "name",
381
order: "asc"
382
}],
383
debounce: 500,
384
pagination: {
385
pageSize: 50,
386
mode: "server"
387
}
388
});
389
390
const [searchValue, setSearchValue] = useState("");
391
392
const handleSearch = (value: string) => {
393
setSearchValue(value);
394
onSearch(value);
395
};
396
397
return (
398
<div className="category-select">
399
<input
400
type="text"
401
placeholder="Search categories..."
402
value={searchValue}
403
onChange={(e) => handleSearch(e.target.value)}
404
/>
405
406
<select
407
value={value || ""}
408
onChange={(e) => onChange(parseInt(e.target.value))}
409
disabled={query.isLoading}
410
>
411
<option value="">Select a category</option>
412
{options.map(option => (
413
<option key={option.value} value={option.value}>
414
{option.label}
415
</option>
416
))}
417
</select>
418
419
{query.isLoading && <span>Loading...</span>}
420
{query.error && <span>Error loading categories</span>}
421
</div>
422
);
423
}
424
425
// Usage with multiple selection
426
function MultiCategorySelect({
427
values = [],
428
onChange
429
}: {
430
values?: number[];
431
onChange: (values: number[]) => void;
432
}) {
433
const { options, onSearch } = useSelect<Category>({
434
resource: "categories",
435
optionLabel: "name",
436
optionValue: "id",
437
defaultValue: values
438
});
439
440
const handleToggle = (value: number) => {
441
if (values.includes(value)) {
442
onChange(values.filter(v => v !== value));
443
} else {
444
onChange([...values, value]);
445
}
446
};
447
448
return (
449
<div className="multi-select">
450
<input
451
type="text"
452
placeholder="Search categories..."
453
onChange={(e) => onSearch(e.target.value)}
454
/>
455
456
<div className="options">
457
{options.map(option => (
458
<label key={option.value}>
459
<input
460
type="checkbox"
461
checked={values.includes(option.value as number)}
462
onChange={() => handleToggle(option.value as number)}
463
/>
464
{option.label}
465
</label>
466
))}
467
</div>
468
</div>
469
);
470
}
471
```
472
473
### Advanced Table Features
474
475
#### Infinite Scrolling Tables
476
477
Implementation of infinite scrolling for large datasets using useInfiniteList.
478
479
```typescript { .api }
480
/**
481
* Infinite scrolling table implementation
482
*/
483
interface InfiniteTableConfig<TData> {
484
/** Resource name */
485
resource: string;
486
/** Items per page */
487
pageSize?: number;
488
/** Sorting configuration */
489
sorters?: CrudSorting;
490
/** Filtering configuration */
491
filters?: CrudFilters;
492
/** Threshold for loading more data */
493
threshold?: number;
494
}
495
496
interface InfiniteTableReturnType<TData> {
497
/** All loaded data */
498
data: TData[];
499
/** Whether more data is available */
500
hasNextPage: boolean;
501
/** Function to load more data */
502
fetchNextPage: () => void;
503
/** Whether next page is loading */
504
isFetchingNextPage: boolean;
505
/** Total count of items */
506
total?: number;
507
}
508
```
509
510
**Infinite Scroll Example:**
511
512
```typescript
513
import { useInfiniteList } from "@refinedev/core";
514
import { useEffect, useRef } from "react";
515
516
function InfinitePostsList() {
517
const {
518
data,
519
hasNextPage,
520
fetchNextPage,
521
isFetchingNextPage
522
} = useInfiniteList({
523
resource: "posts",
524
pagination: {
525
pageSize: 20
526
}
527
});
528
529
const loadMoreRef = useRef<HTMLDivElement>(null);
530
531
useEffect(() => {
532
const observer = new IntersectionObserver(
533
(entries) => {
534
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
535
fetchNextPage();
536
}
537
},
538
{ threshold: 1.0 }
539
);
540
541
if (loadMoreRef.current) {
542
observer.observe(loadMoreRef.current);
543
}
544
545
return () => observer.disconnect();
546
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
547
548
return (
549
<div className="infinite-list">
550
{data?.map(post => (
551
<div key={post.id} className="post-item">
552
<h3>{post.title}</h3>
553
<p>{post.content}</p>
554
</div>
555
))}
556
557
<div ref={loadMoreRef} className="load-more">
558
{isFetchingNextPage && <div>Loading more...</div>}
559
{!hasNextPage && data?.length > 0 && <div>No more posts</div>}
560
</div>
561
</div>
562
);
563
}
564
```
565
566
### Table Export & Import
567
568
#### Export Functionality
569
570
Export table data to various formats with customizable options.
571
572
```typescript { .api }
573
/**
574
* Export table data to files
575
* @param params - Export configuration
576
* @returns Export function and state
577
*/
578
function useExport<TData = BaseRecord>(
579
params?: UseExportConfig<TData>
580
): UseExportReturnType<TData>;
581
582
interface UseExportConfig<TData> {
583
/** Resource name */
584
resource?: string;
585
/** Export format */
586
format?: "csv" | "xlsx" | "json" | "pdf";
587
/** Fields to export */
588
fields?: Array<keyof TData>;
589
/** Custom field mappers */
590
mapData?: (data: TData[]) => any[];
591
/** Export filters */
592
filters?: CrudFilters;
593
/** Export sorting */
594
sorters?: CrudSorting;
595
/** Maximum records to export */
596
maxItemCount?: number;
597
/** Custom filename */
598
filename?: string;
599
}
600
601
interface UseExportReturnType<TData> {
602
/** Trigger export function */
603
triggerExport: () => Promise<void>;
604
/** Whether export is in progress */
605
isLoading: boolean;
606
/** Export error if any */
607
error?: Error;
608
}
609
```
610
611
#### Import Functionality
612
613
Import data from files with validation and error handling.
614
615
```typescript { .api }
616
/**
617
* Import data from files
618
* @param params - Import configuration
619
* @returns Import function and state
620
*/
621
function useImport<TData = BaseRecord>(
622
params?: UseImportConfig<TData>
623
): UseImportReturnType<TData>;
624
625
interface UseImportConfig<TData> {
626
/** Resource name */
627
resource?: string;
628
/** Supported file types */
629
acceptedFileTypes?: string[];
630
/** Custom data mapper */
631
mapData?: (data: any[]) => TData[];
632
/** Batch size for import */
633
batchSize?: number;
634
/** Validation function */
635
validate?: (data: TData[]) => ValidationResult[];
636
}
637
638
interface UseImportReturnType<TData> {
639
/** File input handler */
640
inputProps: {
641
type: "file";
642
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
643
accept: string;
644
};
645
/** Whether import is in progress */
646
isLoading: boolean;
647
/** Import progress (0-100) */
648
progress: number;
649
/** Import errors */
650
errors: ImportError[];
651
/** Successful imports count */
652
successCount: number;
653
}
654
655
interface ImportError {
656
/** Row number with error */
657
row: number;
658
/** Error message */
659
message: string;
660
/** Field with error */
661
field?: string;
662
}
663
664
interface ValidationResult {
665
/** Whether validation passed */
666
valid: boolean;
667
/** Error messages */
668
errors: string[];
669
/** Row index */
670
index: number;
671
}
672
```
673
674
## Types
675
676
```typescript { .api }
677
interface TableColumn<TData> {
678
/** Column key */
679
key: keyof TData;
680
/** Column title */
681
title: string;
682
/** Whether column is sortable */
683
sortable?: boolean;
684
/** Whether column is filterable */
685
filterable?: boolean;
686
/** Column width */
687
width?: string | number;
688
/** Custom render function */
689
render?: (value: any, record: TData, index: number) => React.ReactNode;
690
/** Filter configuration */
691
filterConfig?: {
692
type: "text" | "select" | "date" | "number";
693
options?: Array<{ label: string; value: any }>;
694
};
695
}
696
697
interface TableAction<TData> {
698
/** Action key */
699
key: string;
700
/** Action label */
701
label: string;
702
/** Action icon */
703
icon?: React.ReactNode;
704
/** Action handler */
705
onClick: (record: TData) => void;
706
/** Whether action is disabled */
707
disabled?: (record: TData) => boolean;
708
/** Action color/style */
709
variant?: "primary" | "secondary" | "danger";
710
}
711
712
interface PaginationConfig {
713
/** Current page */
714
current: number;
715
/** Page size */
716
pageSize: number;
717
/** Total items */
718
total: number;
719
/** Available page sizes */
720
pageSizeOptions?: number[];
721
/** Whether to show size changer */
722
showSizeChanger?: boolean;
723
/** Whether to show total */
724
showTotal?: boolean;
725
/** Custom total renderer */
726
showTotalRenderer?: (total: number, range: [number, number]) => React.ReactNode;
727
}
728
729
interface SortConfig {
730
/** Field to sort by */
731
field: string;
732
/** Sort direction */
733
order: "asc" | "desc";
734
/** Sort priority for multi-column sort */
735
priority?: number;
736
}
737
738
interface FilterConfig {
739
/** Field to filter */
740
field: string;
741
/** Filter operator */
742
operator: CrudOperators;
743
/** Filter value */
744
value: any;
745
/** Filter label for display */
746
label?: string;
747
}
748
```