Capítulo 44 de 54
rowSelectionFeature tracks a { [rowId]: boolean } map with built-in Shift-range selection and sub-row cascade behavior — the two things most worth getting right up front are a meaningful getRowId (selection is keyed by id, not index) and deciding how selecting/deselecting a parent should affect its children.
RowSelectionState = Record<string, boolean>. Reads: table.state.rowSelection (reactive), table.atoms.rowSelection.get() (snapshot, no subscription). Row-model-aware selected-rows views: table.getSelectedRowModel(), getFilteredSelectedRowModel(), getGroupedSelectedRowModel().getRowId matters more here than almost anywhere else — the default row id is row.index, which is meaningless once rows re-sort/re-filter/re-fetch. Set getRowId: (row) => row.uuid (or similar) so selection survives those changes. Because selection state is just an id map, it can validly hold ids not present in the current data — useful under manualPagination, where getSelectedRowModel() only ever reflects the current page's rows even though selection itself can span pages.atoms: { rowSelection: atom } (v9-recommended, e.g. for reading selected ids elsewhere to drive bulk-action API calls) or classic state.rowSelection + onRowSelectionChange.enableRowSelection (boolean or per-row predicate, e.g. adults-only); enableMultiRowSelection: false for radio-button-style single selection (or a predicate for conditional single-select); enableSubRowSelection (boolean or predicate) — false stops a parent-row toggle from cascading to children.{ deselectParents: true } to row.toggleSelected(false, opts) / row.getToggleSelectedHandler(opts) to prune stale ancestor ids on deselect. When sub-row selection is blocked for a parent, both select-all APIs (toggleAllRowsSelected/toggleAllPageRowsSelected) skip its descendants, and both "is everything selected" checks (getIsAllRowsSelected/getIsAllPageRowsSelected) ignore them too.enableRowRangeSelection: false disables it; isRowRangeSelectionEvent swaps the modifier check (defaults to event.shiftKey/event.nativeEvent.shiftKey).selectChildren: false (option on the toggle handler) restricts a range toggle to rows literally in the display-order interval, instead of recursively cascading into a parent's descendants.resetRowSelection(), either select-all API, and table.reset() clear the anchor explicitly; direct row.toggleSelected()/table.setRowSelection() calls (including externally-controlled state changes) do not move or establish it.row.getToggleSelectedHandler() on each row checkbox, table.getToggleAllRowsSelectedHandler()/getToggleAllPageRowsSelectedHandler() on a header "select all" checkbox, row.getIsSomeSelected()/table.getIsSomeRowsSelected() driving an indeterminate visual state; (2) whole-row click — bind row.getToggleSelectedHandler() directly to the <tr>'s onClick instead of a dedicated checkbox column.const features = tableFeatures({ rowSelectionFeature })
const table = useTable({ features, columns, data, getRowId: (row) => row.uuid })
// select-all header + per-row checkbox, sub-row aware
header: ({ table }) => <Checkbox checked={table.getIsAllRowsSelected()}
indeterminate={table.getIsSomeRowsSelected()} onChange={table.getToggleAllRowsSelectedHandler()} />
cell: ({ row }) => <Checkbox
checked={row.getIsSelected() || (row.getCanSelectSubRows() && row.getIsAllSubRowsSelected())}
disabled={!row.getCanSelect()} indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()} />
// pruning stale parent ids on child deselect
row.getToggleSelectedHandler({ deselectParents: true })
getCanSelectSubRows()/getIsAllSubRowsSelected() only matter with hierarchical data — flat tables just need row.getIsSelected()), and the deselectParents option for keeping selection state tidy.getRowId before anything else — selection keyed by array index breaks the moment rows re-sort or re-fetch.deselectParents deliberately — the default (parent id sticks around after a child is deselected) surprises people who expect selection to always mirror what's visually checked.manualPagination, remember getSelectedRowModel() only reflects the current page — selection itself can (and often should) span pages via ids not present in data.getRowId in full detail.