or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application-setup.mdauthentication.mddata-operations.mdforms.mdindex.mdnavigation.mdtables-lists.mdutilities.md

tables-lists.mddocs/

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

```