Ant Design tables
Version baseline: Ant Design 6.x (6.4.3 current stable at this review), with the current v6 documentation and React 18 or 19 compatibility; prefer React 19 for new work. Inspect the exact installed patch and migration notes before changing an existing project.
Separate table view state from the data-fetching contract. A table should have stable identity, explicit loading and empty states, and a clear answer to whether sorting, filtering, and pagination happen locally or on the server.
Workflow
- Define the row type and choose a stable
rowKeyfrom the domain. Never use the array index when rows can be inserted, removed, or reordered. - Build typed columns with small, pure renderers. Keep formatting out of fetch effects and avoid recreating expensive column definitions on every render.
- Decide the state model:
- Local data: let the table derive pagination, filters, and sorting from the loaded rows.
- Server data: control
pagination, fetch fromonChange, and preserve the full query state in the URL or page state.
- For server data, reset to page one when a filter or sort changes, pass the real
total, showloading, and guard against stale responses when requests overlap. - Add selection only when a bulk action exists. Keep selected keys stable across refreshes when the product requires it, and explain unavailable or already-processed rows.
- Verify loading, empty, error, long content, narrow widths, keyboard focus, sorting, filtering, page changes, refreshes, and selection after data changes.
type User = { id: string; name: string; status: "active" | "invited" }
const columns: TableProps<User>["columns"] = [
{ title: "Name", dataIndex: "name", key: "name" },
{ title: "Status", dataIndex: "status", key: "status" },
]
<Table<User>
rowKey="id"
columns={columns}
dataSource={rows}
loading={isLoading}
pagination={{ current: page, pageSize, total }}
onChange={(pagination, filters, sorter) => {
setQuery({ page: pagination.current ?? 1, pageSize, filters, sorter })
}}
/>
Review traps
- Do not mix
defaultCurrentwith a controlledcurrentvalue. - Do not show a successful empty state while the first request is still loading.
- Reset incompatible selection when the dataset or filter scope changes.
- Use
scroll.xor a responsive column strategy for wide data, not clipped text. - Keep action buttons explicit and accessible; put destructive bulk actions behind the appropriate confirmation flow.