ProTable
pro-tableCombines business-table columns, querying, sorting, selection, pagination, and toolbar actions.
Usage
Hosted Mode
Pass request, that is, ProTable will manage page/sort/filters/loading/data by itself and request the server on demand.
No data | ||||||
const request = async (p) => {
const rows = await fetchEmployees(p.filters, p.sort);
const start = (p.page - 1) * p.pageSize;
return { data: rows.slice(start, start + p.pageSize), total: rows.length };
};
<ProTable
title="Employee List"
columns={columns}
request={request}
defaultPageSize={8}
enableRowSelection
search={{ fields: searchFields }}
/>Default sorting + fixed query parameters
defaultSorting allows request to be sorted for the first time; params is the condition for nailing the page context, and the shallow comparison changes will return to page 1 for rechecking. request/params can all be inlined and no requests will be repeated.
No data | |||||
<ProTable
title="Default is in descending order of monthly salary"
columns={columns}
defaultSorting={[{ id: "salary", desc: true }]}
params={{ dept }}
request={async (p) => {
const { rows, total } = await api.list({
page: p.page,
pageSize: p.pageSize,
sort: p.sort,
...p.filters,
...p.params,
});
return { data: rows, total };
}}
/>cursor Paging
paginationMode="cursor", request returns { data, nextCursor, hasMore }, with the previous/next page at the bottom.
No data | |||||
<ProTable
title="Log"
columns={columns}
request={request}
paginationMode="cursor"
defaultPageSize={8}
pageSizeOptions={[8, 16, 32]}
/>Display mode (controlled paging)
When managing data/paging, just pass data + pagination + search callback.
| Employees 01 | 1 | R&D Department | Working | 2024-01-15 | ¥8,000 | |
| Employees 02 | 2 | Marketing Department | Resignation | 2024-02-15 | ¥9,500 | |
| Employees 03 | 3 | Finance Department | To be hired | 2024-03-15 | ¥11,000 | |
| Employees 04 | 4 | Human Resources Department | Working | 2024-04-15 | ¥12,500 | |
| Employees 05 | 5 | R&D Department | Resignation | 2024-05-15 | ¥14,000 | |
| Employees 06 | 6 | Marketing Department | To be hired | 2024-06-15 | ¥15,500 | |
| Employees 07 | 7 | Finance Department | Working | 2024-07-15 | ¥17,000 | |
| Employees 08 | 8 | Human Resources Department | Resignation | 2024-08-15 | ¥18,500 |
<ProTable
title="Employee List"
columns={columns}
data={pageData}
enableRowSelection
onReload={reload}
toolbarActions={<Button size="sm">+ New employee</Button>}
search={{ fields: searchFields, onSearch, onReset }}
pagination={{ page, pageSize, total, onPageChange: setPage }}
/>Simplified table
Compact density + full screen button off + no query area.
| Employees 01 | 1 | R&D Department | Working |
| Employees 02 | 2 | Marketing Department | Resignation |
| Employees 03 | 3 | Finance Department | To be hired |
| Employees 04 | 4 | Human Resources Department | Working |
| Employees 05 | 5 | R&D Department | Resignation |
<ProTable
title="Compact Table"
columns={columns.slice(0, 4)}
data={rows}
density="compact"
toolbar={{ fullscreen: false }}
/>When to use
Use ProTable for a complete enterprise list page: search form, toolbar, table, and pagination in one component. Prefer it for server pagination, sorting, and filtering. Table is the lower-level table skin; ProTable's managed mode also owns the request lifecycle.
Import
import { ProTable } from "@hulianui/ui"Props
Inherits Omit<TableProps<TData>, "data">, including columns, sorting, selection, density, row identity, and row classes, and adds:
Enablevirtualfor large result sets. It is inherited from Table even though it is not repeated in the table below:tsx <ProTable columns={columns} request={fetchRows} virtual={{ enabled: true, height: 480 }} />See Table `virtual` for parameters and constraints. It requires@tanstack/react-virtualand is not recommended with trees, expanded detail panels, or row drag-and-drop.
| Name | Type | Default | Description |
|---|---|---|---|
| data | TData[] | - | Required in display mode; ignored when request enables managed mode. |
| request | (params: ProTableRequestParams) => Promise<ProTableRequestResult<TData>> | - | Enables managed data, paging, sorting, filters, loading, and selection. Held in a ref, so function identity is not a request dependency. |
| params | Record<string, unknown> | - | Fixed managed-mode parameters. Shallow changes reset to page one and re-request; values remain separate from filters. |
| paginationMode | "page" | "cursor" | "page" | Page mode returns {data,total}; cursor mode receives a cursor and returns {data,nextCursor,hasMore}. |
| defaultPageSize | number | 10 | Initial managed page size. |
| defaultSorting | SortingState | [] | Initial managed sorting, read only on first mount and sent with the first request. |
| pageSizeOptions | number[] | - | Renders a page-size selector when supplied. |
| pagination | ProTablePagination | - | Display-mode footer pagination: {page,pageSize,total,onPageChange,showFirstLast?,onPageSizeChange?}. |
| search | Omit<SearchFormProps,"onSearch"> & { onSearch? } | - | Integrated SearchForm; onSearch is optional in managed mode. |
| toolbar | boolean | ProTableToolbarFeatures | true | True enables all tools, false hides the toolbar, or configure reload, density, column settings, and fullscreen individually. |
| loading | boolean | - | Display-mode loading state, including the rotating refresh icon. |
| actionRef | Ref<ProTableActions> | - | Exposes reload() and clearSelection(). |
| columnVisibility | Record<string, boolean> | - | Controlled column visibility, mapping column id to visibility, where a missing key means visible. It follows the rowSelection and sorting contract: supplying it takes control and requires onColumnVisibilityChange, omitting it keeps internal state. Column ids come from ColumnDef.id, falling back to accessorKey. Columns carrying meta.lockVisible stay visible, so writing false for them has no effect. |
| rootClassName | string | - | Outer container class, distinct from the Table className. |
Events
Inherited Table events include sorting, selection, expansion, and column-filter changes. ProTable adds:
| Event | Type | Description |
|---|---|---|
| onReload | () => void | Called from the toolbar reload control. |
| onRequestError | (error: unknown) => void | Managed request failure handler; defaults to console.error, resets loading, and preserves previous data. |
| onColumnVisibilityChange | (next: Record<string, boolean>) => void | Column-visibility change. It reports the complete next map, not a patch, so it can be written straight to local storage or PATCHed back to the server. |
Slots
| Slot | Type | Description |
|---|---|---|
| title | ReactNode | Card title on the left side of the toolbar. |
| toolbarActions | ReactNode | Custom actions before the built-in toolbar controls. |
| batchActions | (ctx: ProTableBatchCtx) => ReactNode | Renders batch operations when selection is enabled and rows are selected. |
Usage notes
- Memoize `columns` (same root cause as Table): TanStack's
flexRenderrenders a functioncellas a component type, so a changed identity unmounts and remounts the cell. With an input inside, a controlled field loses focus on every keystroke and anonBlursubmit fires on the remount blur, committing a half-typed value. Never put a per-keystroke value in theuseMemodependencies, and prefer uncontrolled inputs for inline editing.
- In managed mode,
data,pagination, andloadingare ignored. Cursor mode has no total or random page jump; changing filters, sorting, or page size resets to page one. - Supply
getRowIdin managed mode so selection remains stable across pages.batchActionsalso requires enabled selection and at least one selected row. - Row selection is controlled by whether you pass `rowSelection`, not by whether the table is managed. Passing it makes selection controlled, so pass
onRowSelectionChangetoo. Without it nothing can be selected and only a dev warning says why. Omit both and the component holds selection internally. Through 0.29.0 managed mode always held selection and silently discarded these two props: the table looked completely normal, checkboxes toggled and the header box went indeterminate, yet the consumer state stayed{}until submit produced an empty array (#202). - Column visibility follows the same controlled contract as selection: supplying
columnVisibilitymeans you own it and must also supplyonColumnVisibilityChange, otherwise the column-setting popover does nothing and only warns in development. Without it the preference lives in internal state only, so the toolbar works but a refresh throws it away, and "the same operator switches machines and keeps their columns" cannot be written at all from outside the component. The map means missing equals visible, so persistence only has to record the columns that were switched off. - Mark identity and action columns with `meta.lockVisible`. A blanket on/off list cannot express "these two may not be switched off", and a row without its identity column or its action column has neither a name nor an exit. Locked columns are checked and disabled in the toolbar, and a controlled
falsedoes not apply to them either, otherwise one stale persisted preference could close the exit with no way to reopen it from the UI. - The guard against hiding the last visible column (which would leave an empty header) is still in place; in controlled mode it shows up as the change handler simply not firing.
- Request rejection falls back to
console.error; useonRequestErrorfor a production toast or report. requestis held in a ref. Inline functions do not loop, but replacing only the function does not reload; changeparamsor callactionRef.reload().defaultSortingis an uncontrolled initial value. Later changes do not overwrite user sorting; remount with a key or use controlledsorting.paramsis compared only one level deep. Flat inline objects are safe; keep nested values referentially stable or flatten them.paramsstays separate fromfilters, so fixed constraints cannot be reset or overwritten by the search form. Merge them explicitly insiderequest.- Any
paramscontent change resets page mode to page one and cursor mode to the start.
Related
Table · Book3D · PricingTable · JsonViewer · EditableTable · List