# rn-data-table URL: /docs/native/docyrus/data-table Headless TanStack table renderer for React Native — FlashList body, sticky header, frozen columns, row grouping, skeleton loading and a pagination footer, with a data/columns convenience path. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-data-table ``` **Dependencies:** - [@tanstack/react-table](https://www.npmjs.com/package/@tanstack/react-table) - [@shopify/flash-list](https://www.npmjs.com/package/@shopify/flash-list) ## Usage ### Headless (web API) Like the web `DataTable`, the native component renders a TanStack `table` instance you build elsewhere — typically with `useDocyrusDataTable`, or directly with `useReactTable`. ```tsx import { getCoreRowModel, getPaginationRowModel, getSortedRowModel, useReactTable, type ColumnDef } from '@tanstack/react-table'; import { DataTable, getDataTableSelectColumn } from '@/components/docyrus-native/data-table'; const columns: ColumnDef[] = [ getDataTableSelectColumn(), { accessorKey: 'name', header: 'Name', size: 160 }, { accessorKey: 'role', header: 'Role' }, { accessorKey: 'status', header: 'Status' } ]; function PeopleTable({ people, isLoading }: { people: Person[]; isLoading: boolean }) { const table = useReactTable({ data: people, columns, getRowId: row => row.id, enableRowSelection: true, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getPaginationRowModel: getPaginationRowModel() }); return ( router.push(`/people/${row.original.id}`)} /> ); } ``` ### Convenience path Without `table`, `DataTable` builds the instance itself from `data` + `columns` and the `enable*` flags / controlled state pairs. ```tsx ``` ### Layout notes - The table card needs a **bounded height**: pass `maxHeight`, or render it in a parent with a fixed / flex height. The body is a FlashList (always virtualized). - The header stays visible while the body scrolls (`stickyHeader`, default `true`) and uses a muted background. - Columns pinned left (`columnPinning.left`) — plus the reserved `select` and `actions` columns, which are always frozen on the leading edge — stay in place while the table scrolls horizontally. `columnPinning.right` columns freeze on the trailing edge. - When the columns are narrower than the screen, the scrolling (non-pinned) columns stretch to fill it. - Grouped rows render as full-width group headers (chevron, value visual — avatar / image / icon / colour dot — label and row count). Tap a header to expand / collapse. Labels come from `resolveGroupHeaderPresentation` (the same helper as the web data grid), and `meta.renderGroupValue` is honoured except for date buckets. - **Tap** a row → `onRowClick(row)`. **Long-press** a row → toggles its selection (when the row can be selected). Interactive children (checkboxes, links, buttons) keep their own presses. ## API Reference ### DataTable — shared props | Prop | Type | Default | Description | |------|------|---------|-------------| | `table` | `Table` | — | External TanStack table instance (headless mode). When set, the convenience props below are not accepted. | | `className` | `string` | — | Outer wrapper (table card + pagination footer) | | `containerClassName` | `string` | — | The bordered table card | | `tableClassName` | `string` | — | The horizontally scrolling content (header + rows) | | `headerClassName` | `string` | — | Header row (also applied to pinned header cells) | | `bodyClassName` | `string` | — | The FlashList body wrapper | | `rowClassName` | `string \| ((row: Row) => string \| undefined)` | — | Per-row classes | | `cellClassName` | `string` | — | Applied to every body cell, in addition to `columnDef.meta.cellClassName` | | `emptyText` | `string` | `'No results.'` | Empty-state text (`ui.dataTable.noResults`) | | `isLoading` | `boolean` | `false` | Render skeleton rows instead of data | | `loadingText` | `string` | — | Accessible label and caption under the skeleton rows | | `pagination` | `boolean \| DataTableLegacyPagination` | `false` | `true` renders a footer driven by `table.getState().pagination` (honours `manualPagination` + `rowCount`). The legacy object is accepted by the convenience path only. | | `pageSizeOptions` | `ReadonlyArray` | `DATA_GRID_PAGE_SIZE_OPTIONS` | Page-size choices in the footer's page-size sheet | | `onRowClick` | `(row: Row) => void` | — | Fired when a non-group row is pressed | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Cell padding and text size | | `variant` | `'default' \| 'outlined' \| 'elevated'` | `'default'` | Card style | | `zebra` | `boolean` | `false` | Alternate row backgrounds | | `rowDividers` | `boolean` | `true` | Divider line between rows | | `columnDividers` | `boolean` | `false` | Divider line between columns | | `stickyHeader` | `boolean` | `true` | Keep the header visible while the body scrolls. `false` scrolls it away with the rows. | | `maxHeight` | `number` | — | Maximum height of the table card | | `maxCellCharacters` | `number` | — | Truncate plain-text cell content after N characters | | `skeletonRowCount` | `number` | `8` | Number of skeleton rows while `isLoading` | | `testID` | `string` | — | Test identifier on the outer wrapper | ### DataTable — convenience props (no `table`) | Prop | Type | Default | Description | |------|------|---------|-------------| | `data` | `TData[]` | — | Table data (required on this path) | | `columns` | `ColumnDef[]` | — | Column definitions (required on this path) | | `getRowId` | `(row: TData) => string` | `row.id`, else the index | Row id accessor (selection / pinning state keys) | | `getSubRows` | `(row: TData) => TData[] \| undefined` | `row.subRows` | Sub-row accessor, used when `enableExpanding` | | `enableSorting` | `boolean` | `true` | Tap a header to cycle its sort | | `enableFiltering` | `boolean` | `false` | Apply `columnFilters` | | `enableGlobalFilter` | `boolean` | `false` | Apply `globalFilter` | | `enableColumnVisibility` | `boolean` | — | Informational; visibility follows `columnVisibility` | | `enableRowSelection` | `boolean` | `false` | Prepends a pinned `getDataTableSelectColumn()` (unless `columns` already has a `select` column); a row press also toggles selection | | `enableMultiRowSelection` | `boolean` | `false` | Allow selecting several rows | | `enableColumnPinning` | `boolean` | `false` | Honour `columnPinning` | | `enableColumnResizing` | `boolean` | — | Accepted for compatibility; no-op on touch | | `enableGrouping` | `boolean` | `false` | Honour `grouping`. Columns without `getGroupingValue` get a stable key for object values, so expanded enum / relation objects no longer collapse into one `[object Object]` group. | | `enableExpanding` | `boolean` | `false` | Expandable sub-rows (chevron + indentation in the first data column) | | `enableRowPinning` | `boolean` | `false` | Honour `rowPinning` (pinned rows render at the top / bottom with an accent rule) | | `sorting` / `onSortingChange` | `SortingState` / `OnChangeFn` | — | Controlled sorting | | `columnFilters` / `onColumnFiltersChange` | `ColumnFiltersState` / `OnChangeFn` | — | Controlled column filters | | `globalFilter` / `onGlobalFilterChange` | `string` / `OnChangeFn` | — | Controlled global filter | | `columnVisibility` / `onColumnVisibilityChange` | `VisibilityState` / `OnChangeFn` | — | Controlled visibility (grouped columns are hidden automatically) | | `rowSelection` / `onRowSelectionChange` | `RowSelectionState` / `OnChangeFn` | — | Controlled selection | | `expanded` / `onExpandedChange` | `ExpandedState` / `OnChangeFn` | — | Controlled expanded rows / groups | | `columnPinning` / `onColumnPinningChange` | `ColumnPinningState` / `OnChangeFn` | — | Controlled column pinning | | `grouping` / `onGroupingChange` | `GroupingState` / `OnChangeFn` | — | Controlled grouping | | `rowPinning` / `onRowPinningChange` | `RowPinningState` / `OnChangeFn` | — | Controlled row pinning | Controlled `on*Change` handlers always receive the resolved next value (never an updater function). ### DataTableLegacyPagination | Prop | Type | Default | Description | |------|------|---------|-------------| | `enabled` | `boolean` | — | Enable pagination | | `pageSize` | `number` | `10` | Rows per page (client paging) | | `pageIndex` | `number` | `0` | Initial page index (zero-based) | | `currentPage` | `number` | — | Current page (one-based) for server paging | | `itemsPerPage` | `number` | — | Page size for server paging | | `totalItems` | `number` | — | Total row count — enables server paging (no client slicing) | | `showFirstLast` | `boolean` | `true` | Show first / last buttons | | `showPrevNext` | `boolean` | — | Ignored — previous / next are always shown | | `showPageNumbers` | `boolean` | `true` | Show page-number buttons (otherwise `page / total`) | | `maxPageNumbers` | `number` | `5` | Maximum visible page numbers | | `onPaginationChange` | `(pageIndex: number, pageSize: number) => void` | — | Page change callback | ### getDataTableSelectColumn `getDataTableSelectColumn(options?)` returns the reserved `select` column (header checkbox toggles all page rows; cell checkbox toggles the row). `DataTable` always freezes it on the leading edge. | Option | Type | Default | Description | |--------|------|---------|-------------| | `enableRowNumbers` | `boolean` | `false` | Show the 1-based row number until the row is selected (tap the number to select) | | `size` | `number` | `44` | Column width | | `enableHiding` | `boolean` | `false` | Allow hiding the column | | `enableSorting` | `boolean` | `false` | Allow sorting the column | | …rest | `Partial>` | — | Any other column option except `id` / `header` / `cell` | ### DataTablePagination Presentational footer (also used by the convenience path's legacy pagination). | Prop | Type | Default | Description | |------|------|---------|-------------| | `currentPage` | `number` | — | 1-based current page | | `totalPages` | `number` | — | Page count | | `onPageChange` | `(page: number) => void` | — | Receives the 1-based target page | | `startItem` / `endItem` / `totalItems` | `number` | — | Range summary (`{start}–{end} of {total}`) | | `pageSize` | `number` | — | Current page size (shows the page-size picker with `onPageSizeChange`) | | `pageSizeOptions` | `ReadonlyArray` | `DATA_GRID_PAGE_SIZE_OPTIONS` | Page-size choices | | `onPageSizeChange` | `(pageSize: number) => void` | — | Page-size change handler | | `showFirstLast` | `boolean` | `true` | Show first / last buttons | | `showPageNumbers` | `boolean` | `true` | Page-number buttons (otherwise `page / total`) | | `maxPageNumbers` | `number` | `5` | Maximum visible page numbers | | `alwaysShow` | `boolean` | `false` | Render even when there is a single page | | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button / text size | | `className` | `string` | — | Container classes | ### DataTablePaginationFooter `` renders `DataTablePagination` from the table's own pagination state (`getRowCount()`, `setPageIndex`, `setPageSize`). Props: `table`, `size?`, `pageSizeOptions?`, `className?`. This is what `pagination` renders. ### DataTableToolbar Native-only convenience toolbar for the data/columns path. Headless tables should use the data-grid toolbar menus. | Prop | Type | Default | Description | |------|------|---------|-------------| | `globalFilter` | `string` | — | Current search value | | `onGlobalFilterChange` | `(value: string) => void` | — | Shows the search input when set | | `searchPlaceholder` | `string` | `'Search...'` | Search placeholder (`ui.common.searchPlaceholder`) | | `enableColumnVisibility` | `boolean` | — | Shows the **Fields** button (opens a checklist sheet) | | `columns` | `{ id: string; label: string; visible: boolean }[]` | — | Column visibility list | | `onColumnVisibilityChange` | `(columnId: string, visible: boolean) => void` | — | Visibility change handler | | `actions` | `ReactNode` | — | Custom trailing actions | | `className` | `string` | — | Container classes | ## Translation keys `ui.dataTable.noResults`, `ui.dataTable.loading`, `ui.dataTable.expandGroup`, `ui.dataTable.collapseGroup`, `ui.dataTable.ungrouped`, `ui.dataGrid.pagination.{empty,range,pageSize,first,prev,pageOf,next,last}`, `ui.dataGrid.fields`, `ui.common.searchPlaceholder`. ## Breaking changes (native major) - `styles`, `headerStyle`, `bodyStyle`, `rowStyle`, `cellStyle`, `toolbarStyle` (all `any`) were **removed** — use `className`, `containerClassName`, `tableClassName`, `headerClassName`, `bodyClassName`, `rowClassName` and `cellClassName`. - `onRowClick` now receives the TanStack `Row` (web parity) instead of the raw record: use `row.original`. - `stickyHeader` now defaults to `true`, and the body is always a FlashList (the non-virtualized ScrollView path and the "mobile cards" fallback were removed). - `DataTableProps` no longer extends `ViewProps` (no `...rest` spread onto the container); `testID` is still supported. - `data` / `columns` are no longer required when `table` is passed; `DataTableProps` is now a union of `DataTableHeadlessProps` and `DataTableConvenienceProps`. - With `enableRowSelection`, the selection checkbox is now a real `select` column (`getDataTableSelectColumn`) instead of an extra 50px cell, and a long-press toggles selection. - Expanding uses TanStack's expanded row model (`getSubRows`, keyed by row id) instead of rendering `original.subRows` manually. - Legacy `pagination.showPrevNext` is ignored. ## Components | Component | Description | |-----------|-------------| | `DataTable` | Headless table renderer + convenience path | | `DataTablePagination` | Presentational pagination footer | | `DataTablePaginationFooter` | Pagination footer bound to a table's state | | `DataTableToolbar` | Search / fields / actions toolbar (convenience path) | | `getDataTableSelectColumn` | Factory for the reserved `select` column | ## Type Exports | Type | Description | |------|-------------| | `DataTableProps` | `DataTableHeadlessProps \| DataTableConvenienceProps` | | `DataTableBaseProps` | Props shared by both paths | | `DataTableHeadlessProps` | Props with an external `table` | | `DataTableConvenienceProps` | Props for the `data` + `columns` path | | `DataTableLegacyPagination` | Legacy pagination object | | `DataTablePaginationProps` | Props for `DataTablePagination` | | `GetDataTableSelectColumnOptions` | Options for `getDataTableSelectColumn` | | `DataTableToolbarProps` | Props for `DataTableToolbar` | | `ColumnDef` | Re-exported from `@tanstack/react-table` |