# useDocyrusDataGrid URL: /docs/native/hooks/use-docyrus-data-grid Everything a Docyrus-backed native DataGrid needs in one hook — columns from the schema, server-side filters / sort / search / paging, saved views, pivot filters, advanced AND/OR filter, borrowed relation columns, inline edit, status updates, bulk actions, exports and persisted view parameters. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-data-grid ``` **Dependencies:** - [@docyrus/app-utils](https://www.npmjs.com/package/@docyrus/app-utils) - [@docyrus/api-client](https://www.npmjs.com/package/@docyrus/api-client) - [@tanstack/react-query](https://tanstack.com/query/latest) - [@tanstack/react-table](https://tanstack.com/table/latest) - [@react-querybuilder/core](https://react-querybuilder.js.org) A port of the web hook with the **same option and result names**. It composes [`useDocyrusDataViewSelect`](/docs/native/hooks/use-docyrus-data-view-select) (schema, views, forms) with the native [rn-data-grid](/docs/native/docyrus/data-grid) controller, and returns `table` + `gridProps` for ` ); } ``` ### Bulk update / compose dialogs Like web, the `update` / `email` / `message` bulk actions open the native `BulkUpdateDialog` / `EmailComposeDialog` / `InstantMessageComposeDialog` out of the box, and email / phone cells open the compose dialogs through `tableMeta.emailClient` / `messageClient`. Override any of them: ```tsx const { toolbar, table, gridProps } = useDocyrusDataGrid({ client, appSlug: 'base', dataSourceSlug: 'contact', // optional overrides — omit to use the built-in native dialogs renderBulkUpdateDialog: props => , onBulkMessage: phones => openWhatsApp(phones) }); ``` The dialogs are rendered inside `toolbar`, so render `toolbar` even when you hide its controls. ### Side filters and the vertical-tabs side panel ```tsx const { toolbar, table, gridProps, sideFilters, sidePanel } = useDocyrusDataGrid({ client, appSlug: 'base', dataSourceSlug: 'contact', viewSelectVariant: 'vertical-tabs', enableSideFilters: true, sideFiltersConfig: { columnsConfig: [ { id: 'status', accessor: row => row.status, displayName: 'Status', type: 'option', options: statusOptions }, { id: 'created_on', accessor: row => row.created_on, displayName: 'Created', type: 'date' } ] } }); return ( ); ``` `sideFilters` defaults to `presentation: 'sheet'`: the node is a "Filters (n)" trigger that opens the panel as a bottom sheet. `sideFiltersConfig.presentation: 'inline'` renders web's collapsible column in place (tablets), bound to `sideFiltersExpanded`. The emitted rule group is ANDed into the items query. ## API Reference ### Options (`UseDocyrusDataGridOptions`) Every [`useDocyrusDataViewSelect`](/docs/native/hooks/use-docyrus-data-view-select#options-usedocyrusdataviewselectoptions) option (`client`, `appSlug`, `dataSourceSlug`, `appId`, `dataSource`, `overrideFields`, `mapField`, `staleTime`, `enabled`, `enableDataViews`, `dataSourceExpand`, `enableForms`, `persistActiveView`, `persistKey`, `activeViewStorage`, `defaultRowGroupingColumn`, `systemViews`) is accepted and forwarded, plus: #### Data & query | Option | Type | Default | Description | |--------|------|---------|-------------| | `data` | `TData[]` | — | Pre-resolved rows; skips the items query (search then filters client-side). | | `collection` | `DocyrusDataGridCollection` | — | `{ list, updateMany?, deleteMany? }` — drives fetch, inline save and bulk delete. | | `listParams` | `DocyrusDataGridListParams` | — | Extra items params; `filters` is AND-merged, every other key overrides. | | `pivotFilters` | `DocyrusPivotFilterGroupField[]` | — | Pivot strips stacked on top of `toolbar`; their rule is ANDed into the query and they cross-filter against the grid's filters. | | `defaultLimit` | `number` | `100` | Page size when neither the view nor `listParams` set one. | | `enableItemsQuery` | `boolean` | `data === undefined` | Toggle the internal items query. | | `defaultPagingMode` | `'standard' \| 'virtual-scroll'` | `'virtual-scroll'` | Paging mode when the view carries none. | | `excludeFieldSlugs` | `string[]` | — | Slugs dropped from the schema before anything uses it. | | `fieldEnums` | `Record` | — | Enum options for fields the API returns without any. | | `enableRelationColumns` | `boolean` | `false` | Offer columns borrowed from related data sources. | | `relationIconFields` | `Record` | — | Relation slug → icon / image field on the related data source (projected and shown in the cell). | | `users` | `CellUserOption[]` | — | Tenant users for user cells and the user filter (filtered client-side). | | `persistState` | `boolean \| { storage?: 'session' \| 'local'; key?: string }` | — | Persist manually edited view parameters per active view (`true` → in-memory session store). | #### Columns | Option | Type | Default | Description | |--------|------|---------|-------------| | `showSelectColumn` | `boolean` | `true` | Leading select column (pinned left). | | `enableRowMarkers` | `boolean` | `true` | Row numbers on the select column. | | `selectColumn` | `ColumnDef` | — | Replace the select column. | | `actionsColumn` | `ColumnDef` | — | Column after the select column (pinned left). | | `extraColumns` | `ColumnDef[]` | — | Columns before the field columns. | | `getRowId` | `(row, index, parent?) => string` | — | Row identity. | | `mapColumn` | `(field, defaultColumn) => ColumnDef \| null` | — | Per-field override; `null` skips the field. | | `dynamicLabelTranslator` | `(label: string) => string` | — | Translate string headers + `meta.label` (and borrowed-column labels). | | `dynamicEnumOptionTranslator` | `(option: EnumOption, field: IField) => string` | — | Translate static enum option names (cells, filters, group headers). | #### Grid behaviour (forwarded to `useDataGrid`) | Option | Type | Default | Description | |--------|------|---------|-------------| | `readOnly` | `boolean` | `true` | Editing is also switched on by a view's `inlineEditingEnabled`. | | `trackChanges` | `boolean` | `true` | Save / discard bar (forced on while inline editing). | | `onSaveChanges` | `(changes: RowChange[], data: TData[]) => void \| Promise` | bulk PATCH | Custom save handler. | | `enableSearch` | `boolean` | `false` | Grid in-cell search. | | `enableGrouping` | `boolean` | `true` | Row grouping. | | `rowColorRules` / `cellColorRules` | `DataGridRowColorRule[]` / `DataGridCellColorRule[]` | — | Color rules. | | `getRowLabel` | `(row, rowIndex) => string` | — | Row label (a11y + save bar). | | `onRowAdd` / `onRowsAdd` / `onRowsDelete` / `onDataChange` | `useDataGrid` handlers | — | Forwarded. | | `meta` | `TableMeta` | — | Extra table meta; the hook's own wiring wins. | | `initialState` | `InitialTableState` | — | One-time TanStack defaults. | | `getRelationHref` | `(args) => string \| undefined` | — | URL for a relation cell (opened with `Linking`). | | `onOpenRelation` | `(args) => void` | — | In-app navigation for a relation cell (wins). | | `formatDate` / `formatDateTime` / `formatNumber` | formatters | context | Explicit formatters; default to `DateFormatProvider` / `NumberFormatProvider` when a real provider is mounted. | #### Toolbar | Option | Type | Default | Description | |--------|------|---------|-------------| | `enableViewSelect` | `boolean` | `true` | View picker. | | `viewSelectVariant` | `DataGridViewSelectVariant` | `'horizontal-tabs'` | Picker variant (`'vertical-tabs'` has no native side panel — the picker is omitted). | | `viewSelectMaxVisible` | `number` | — | Max inline tabs. | | `enableSearchInput` | `boolean` | `true` | Search input (debounced into `filterKeyword`). | | `searchPlaceholder` | `string` | `t('ui.common.search', 'Search…')` | Placeholder. | | `searchDebounceMs` | `number` | `300` | Search debounce. | | `enableFilterMenu` | `boolean` | `true` | Filter chips + (server-driven grids) the advanced filter. | | `enableGroupMenu` / `enableSortMenu` / `enableRowHeightMenu` / `enableFieldsMenu` / `enableDisplayMenu` | `boolean` | `true` | Menu triggers. | | `enableGalleryMenus` | `boolean` | `true` | Card-fields + display menus in gallery mode. | | `enableServerExportMenu` | `boolean` | `true` | Server export (`POST /v1/edge/run/query-export`, written + shared). | | `enableClientExportMenu` | `boolean` | `false` | Client export of loaded rows (only when the server export is off). | | `serverExportLimit` | `number` | `10000` | Server export row cap. | | `serverExportExcludedFieldTypes` / `serverExportExcludedSlugs` | `string[]` | internal lists | Server export exclusions. | | `enableReloadButton` | `boolean` | `true` | Records-only reload. | | `onReload` | `() => void` | — | After a reload. | | `toolbarClassName` | `string` | — | Toolbar root classes. | | `toolbarStartContent` / `toolbarEndContent` | `ReactNode` | — | Custom toolbar content. | #### Bulk actions & compose | Option | Type | Default | Description | |--------|------|---------|-------------| | `bulkActions` | `false \| DocyrusDataGridBulkAction[]` | `['update', 'delete', 'export', 'email', 'message']` | Built-in selection actions (`email` / `message` schema-gated). | | `extraBulkActions` | `DataGridAction[]` | — | Appended selection actions. | | `exportColumns` | `'visible' \| 'all' \| string[]` | `'visible'` | Columns of the bulk / client export. | | `exportFileName` | `string` | `dataSourceSlug` | Export file name. | | `renderBulkUpdateDialog` | `(props: DocyrusBulkUpdateDialogRenderProps) => ReactNode` | `BulkUpdateDialog` | **Native.** Replace the default bulk-update dialog. | | `renderEmailComposeDialog` | `(props: DocyrusEmailComposeDialogRenderProps) => ReactNode` | `EmailComposeDialog` | **Native.** Replace the composer for the `email` action and email cells. | | `renderMessageComposeDialog` | `(props: DocyrusMessageComposeDialogRenderProps) => ReactNode` | `InstantMessageComposeDialog` | **Native.** Replace the composer for the `message` action and phone cells. | | `onBulkEmail` | `(addresses: string[], rows: TData[]) => void` | — | **Native.** Handle email yourself (wins over the slot and the default dialog). | | `onBulkMessage` | `(phones: string[], rows: TData[]) => void` | — | **Native.** Handle messages yourself (wins over the slot and the default dialog). | #### Side filters | Option | Type | Default | Description | |--------|------|---------|-------------| | `enableSideFilters` | `boolean` | `false` | Render `sideFilters` and AND its rule group into the items query. Needs `sideFiltersConfig`. | | `sideFiltersConfig` | `DocyrusDataGridSideFiltersConfig` | — | Panel configuration (below). | | `sideFiltersDefaultExpanded` | `boolean` | `true` | Initial expanded state of the inline panel. | | `sideFiltersExpanded` / `onSideFiltersExpandedChange` | `boolean` / `(expanded) => void` | — | Controlled expanded state (inline). | | `sideFiltersWidth` | `number \| string` | `280` | Width of the expanded inline panel; ignored for the sheet. | `DocyrusDataGridSideFiltersConfig`: | Field | Type | Default | Description | |-------|------|---------|-------------| | `columnsConfig` | `ColumnConfig[]` | — | Filter columns (required). | | `strategy` | `'server' \| 'client'` | `'server'` | Filter strategy. | | `defaults` | `SideFilterDefaults` | — | Per-column UI hints. | | `sections` | `SideFilterSectionGroup[]` | — | Named section groups. | | `variant` | `'default' \| 'bordered' \| 'compact'` | `'default'` | Panel variant. | | `title` | `ReactNode` | `'Filters'` | Header title (`null` hides it). | | `showActiveChips` / `showClearAll` | `boolean` | `true` | Active chips / "Clear all". | | `searchable` | `boolean \| string` | — | Search input (column id drives a specific column). | | `clearAllLabel` / `clearLabel` | `string` | — | Label overrides. | | `locale` | `Locale` | `'en'` | Filter UI locale. | | `className` | `string` | — | Panel classes. | | `collapseAriaLabel` / `expandAriaLabel` | `string` | — | Inline collapse / expand labels. | | `collapsedWidth` | `number \| string` | — | Accepted for parity (collapsed bar is full-width). | | `presentation` | `'sheet' \| 'inline'` | `'sheet'` | **Native.** Trigger + bottom sheet, or the inline collapsible panel. | | `triggerLabel` | `string` | `title` → `'Filters'` | **Native.** Sheet trigger label. | #### Gallery, forms, accepted-for-parity | Option | Type | Default | Description | |--------|------|---------|-------------| | `cardConfig` | `DataGalleryCardConfig` | auto-detected | Initial card bindings + render hooks. | | `galleryDisplayConfig` | `Partial` | defaults | Initial display config. | | `onCardClick` | `(record, rowIndex) => void` | — | Card tap. | | `defaultFormLayout` | `Record \| null` | — | Form layout when the view has no bound form. | | `enableColumnReorder`, `showDropdownChevron`, `enablePaste` | `boolean` | — | Desktop affordances, accepted and ignored. | ### Result (`UseDocyrusDataGridResult`) Everything [`useDocyrusDataViewSelect`](/docs/native/hooks/use-docyrus-data-view-select#result-usedocyrusdataviewselectresult) returns (`gridViewSelectProps` wraps the gallery config into view save / create; `fields` honour `excludeFieldSlugs`), plus: | Field | Type | Description | |-------|------|-------------| | `table` | `Table` | TanStack table — `` and the menus. | | `gridProps` | `object` | Spread onto ``: the `useDataGrid` result + `actions`, `isReloading`, `isLoading`, `onClearFilters`, `onRefresh` (pull-to-refresh), `galleryCardConfig`, `galleryDisplayConfig`, `onCardClick`. | | `toolbar` | `ReactNode` | Pivot strips → view picker → search → scrollable menu row, plus the bulk dialogs. | | `pivotFilterRule` | `PivotFilterRule[] \| null` | Combined pivot rule. | | `pivotFiltersStrip` | `ReactNode` | The pivot strip alone (`null` without `pivotFilters`). | | `selectedRows` / `selectedRowCount` | `TData[]` / `number` | Reactive selection. | | `activeViewFormId` / `activeViewForm` | `string \| undefined` / `DataForm \| undefined` | Form bound to the active view. | | `formViewProps` | `DocyrusDataGridFormViewProps` | `{ formLayout, gridColumns?, dataSource }` for `useDocyrusFormView`. | | `items` | `TData[]` | Rows passed to the grid. | | `resolvedListParams` | `DocyrusDataGridListParams` | Params actually sent. | | `pagingMode` | `'standard' \| 'virtual-scroll' \| undefined` | Pass to ``. | | `reload` | `() => void` | Schema + views + items refetch, then `onReload`. | | `sidePanel` | `ReactNode` | `DataGridSidePanel` with the vertical-tabs view picker when `viewSelectVariant === 'vertical-tabs'`, else `null`. | | `sideFilters` | `ReactNode` | `DataTableSideFilters` (sheet trigger by default), `null` unless `enableSideFilters` + config. | | `sideFiltersExpanded` / `setSideFiltersExpanded` | `boolean` / `(v) => void` | Inline panel expanded state. | | `sideFiltersQuery` | `RuleGroupType \| undefined` | Rule group emitted by the side filters. | ## Native deltas - **Toolbar layout** — stacked (pivot strips, view picker, search, one horizontally scrollable row of compact triggers + `toolbarEndContent`) instead of web's single wrapping row. - **Email / phone cells** — tapping opens an action sheet (web: on-hover icons). Like web, `tableMeta.emailClient` / `messageClient` = `client`, so "Compose email" / "Send message" open the grid's own compose dialogs. When `renderEmailComposeDialog` / `onBulkEmail` (resp. message) is supplied, the cells route to that override through `tableMeta.onComposeEmail` / `onSendMessage`. - **Bulk `update` / `email` / `message`** — open the native `BulkUpdateDialog` / `EmailComposeDialog` / `InstantMessageComposeDialog` (bottom sheets). Precedence for email / message: `onBulkEmail` / `onBulkMessage` → `render*ComposeDialog` → default dialog → `Linking` (`mailto:` / `sms:`, only without a client). - **Bulk `export`** — ActionSheet (CSV / Excel / JSON / Markdown); the file is written to the cache directory and shared (optional `expo-file-system` + `expo-sharing`). - **Side filters** — `sideFilters` is the native `DataTableSideFilters`, `presentation: 'sheet'` by default (web: a collapsible column; available as `'inline'`). - **Vertical tabs** — `sidePanel` is a `DataGridSidePanel`: a strip whose view list opens as a bottom sheet on phones, a side column on tablets (≥ 768 dp). - **UUID copy** — uses the optional `expo-clipboard` peer, falling back to the share sheet. - **Storage** — `persistState` uses `lib/storage` (`'session'` = in-memory for the process, `'local'` = the registered persistent store). - **`gridProps.onRefresh`** — pull-to-refresh runs the records-only reload. - `enableColumnReorder`, `showDropdownChevron`, `enablePaste` are ignored; column order / pinning live in the Fields sheet. - New i18n keys: `ui.dataGrid.bulkUpdate`, `ui.dataGrid.bulkDelete`, `ui.dataGrid.bulkEmail`, `ui.dataGrid.bulkMessage`, `ui.dataGrid.reload`, `ui.dataGrid.exportFormatCsv|Xlsx|Json|Markdown` (web hard-codes these labels). ## Exports | Export | Description | |--------|-------------| | `useDocyrusDataGrid` | The hook. | | `collectFieldSlugsByType(fields, types)` | Slugs of fields whose type is in `types`. | | `collectRecipientValues(rows, slugs)` | De-duplicated string values of `slugs` across `rows`. | | `EMAIL_FIELD_TYPES` / `PHONE_FIELD_TYPES` | `Set(['field-email'])` / `Set(['field-phone'])`. | ## Type Exports | Type | Description | |------|-------------| | `UseDocyrusDataGridOptions` / `UseDocyrusDataGridResult` | Options / result. | | `DocyrusDataGridListParams` | Items query params. | | `DocyrusDataGridCollection` | `{ list, updateMany?, deleteMany? }`. | | `DocyrusDataGridBulkAction` | `'update' \| 'delete' \| 'export' \| 'email' \| 'message'`. | | `DocyrusDataGridFormViewProps` | `formViewProps` shape. | | `DocyrusDataGridSideFiltersConfig` | Side-filter config (see Side filters). | | `DataGridPersistSnapshot` | Persisted parameter snapshot. | | `DocyrusBulkUpdateDialogRenderProps` | `renderBulkUpdateDialog` props (`open`, `onOpenChange`, `client`, `appSlug`, `dataSourceSlug`, `records`, `onSuccess`). | | `DocyrusEmailComposeDialogRenderProps` / `DocyrusMessageComposeDialogRenderProps` | Compose slot props (`client`, `trigger: null`, `open`, `onOpenChange`, `to`). |