useDocyrusDataGrid
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
pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-data-gridpnpm add @docyrus/app-utils @docyrus/api-client @tanstack/react-query @tanstack/react-table @react-querybuilder/coreA port of the web hook with the same option and result names. It composes useDocyrusDataViewSelect (schema, views, forms) with the native useDataGrid controller, and returns table + gridProps for <DataGrid> and a ready-made toolbar. It needs an authenticated RestApiClient and a QueryClientProvider above it; mount DocyrusTenantProvider to get tenant date / number formats in every cell.
Overview
- Columns — one TanStack column per schema field (
getCellOpts→ cell variant), the data source's label field synthesized when the API omits it, duplicate slugs deduped, identity / autonumber / metadata-readOnlyfields locked. - Items query —
GET /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/items(orcollection.list) withcolumns(relations projected asslug(id, name, autonumber_id[, iconField][, borrowed…])),expand,orderBy(live sort beats the view preset),filters(saved view + toolbar chips + advanced group + pivot rules +listParams.filters, ANDed), debouncedfilterKeyword, andlimit/offset/fullCountin standard paging. A phantom column reported by the backend is stripped and the request retried. - Saved views —
gridViewSelectPropsdrives the nativeDataGridViewSelect; the active view is applied to the table (visibility, order, pinning withselect/actionskept first, sort, grouping, row height, paging, inline editing, color rules). - Advanced filter —
DataGridAdvancedFilterstores its AND/OR group under the reserved__advanced_filter__column-filter entry, ANDed with the chips. - Borrowed relation columns (
enableRelationColumns) —GET /v1/dev/data-sources/:id/fields?expand=parent; hidden until picked in the Fields sheet, one hop, positive-only filters (rel_<relation>/<field>). - Inline editing — change tracking (save / discard bar) →
PATCH …/items/bulk(orcollection.updateMany); status cells post to…/status-update/{field}throughtableMeta.onStatusUpdate. - Bulk actions —
update/delete/export/email/messageon the selection bar (see Native deltas). - Gallery mode — per-view
galleryCardConfig/galleryDisplayConfig(saved view →cardConfigoption →detectCardConfigFromFields), editable from the toolbar gallery menus and the view editor. - Form binding —
activeViewFormId/activeViewForm/formViewPropsfor a companionuseDocyrusFormView. - Persistence —
persistStatesnapshots search, filters, sort, grouping, row height, display mode, column layout, page size and pivot selection per active view.
Usage
import { View } from 'react-native';
import { useDocyrusClient } from '@docyrus/signin/react-native';
import { DataGrid } from '@/components/docyrus-native/data-grid';
import { useDocyrusDataGrid } from '@/hooks/docyrus-native/use-docyrus-data-grid';
export function OrganizationsScreen() {
const client = useDocyrusClient();
const { table, gridProps, toolbar, pagingMode } = useDocyrusDataGrid({
client: client!,
appSlug: 'base',
dataSourceSlug: 'organization',
persistState: true,
pivotFilters: [{ fieldSlug: 'status' }],
enableRelationColumns: true
});
return (
<View className="flex-1">
{toolbar}
<DataGrid table={table} {...gridProps} pagingMode={pagingMode} />
</View>
);
}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:
const { toolbar, table, gridProps } = useDocyrusDataGrid({
client,
appSlug: 'base',
dataSourceSlug: 'contact',
// optional overrides — omit to use the built-in native dialogs
renderBulkUpdateDialog: props => <MyBulkUpdateSheet {...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
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 (
<View className="flex-1">
{sidePanel}
{toolbar}
<View className="flex-row px-3">{sideFilters}</View>
<DataGrid table={table} {...gridProps} />
</View>
);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<TData>)
Every useDocyrusDataViewSelect 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<TData> | — | { 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<string, { id; name; color? }[]> | — | Enum options for fields the API returns without any. |
enableRelationColumns | boolean | false | Offer columns borrowed from related data sources. |
relationIconFields | Record<string, string> | — | 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<TData> | — | Replace the select column. |
actionsColumn | ColumnDef<TData> | — | Column after the select column (pinned left). |
extraColumns | ColumnDef<TData>[] | — | 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<void> | 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<TData> | — | 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<TData>[] | — | 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<TData> | — | 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<TData>:
| Field | Type | Default | Description |
|---|---|---|---|
columnsConfig | ColumnConfig<TData>[] | — | 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<TData> | auto-detected | Initial card bindings + render hooks. |
galleryDisplayConfig | Partial<DataGalleryDisplayConfig> | defaults | Initial display config. |
onCardClick | (record, rowIndex) => void | — | Card tap. |
defaultFormLayout | Record<string, unknown> | null | — | Form layout when the view has no bound form. |
enableColumnReorder, showDropdownChevron, enablePaste | boolean | — | Desktop affordances, accepted and ignored. |
Result (UseDocyrusDataGridResult<TData>)
Everything useDocyrusDataViewSelect returns (gridViewSelectProps wraps the gallery config into view save / create; fields honour excludeFieldSlugs), plus:
| Field | Type | Description |
|---|---|---|
table | Table<TData> | TanStack table — <DataGrid table> and the menus. |
gridProps | object | Spread onto <DataGrid>: 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 <DataGrid pagingMode>. |
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. WhenrenderEmailComposeDialog/onBulkEmail(resp. message) is supplied, the cells route to that override throughtableMeta.onComposeEmail/onSendMessage. - Bulk
update/email/message— open the nativeBulkUpdateDialog/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 (optionalexpo-file-system+expo-sharing). - Side filters —
sideFiltersis the nativeDataTableSideFilters,presentation: 'sheet'by default (web: a collapsible column; available as'inline'). - Vertical tabs —
sidePanelis aDataGridSidePanel: 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-clipboardpeer, falling back to the share sheet. - Storage —
persistStateuseslib/storage('session'= in-memory for the process,'local'= the registered persistent store). gridProps.onRefresh— pull-to-refresh runs the records-only reload.enableColumnReorder,showDropdownChevron,enablePasteare 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). |
useDocyrusDataGallery
Docyrus-backed card gallery for React Native — composes useDocyrusDataGrid for schema, saved views, server filters / sort / search / paging and pivot / side filters, and adds gallery-owned card + display config, a toolbar and props for the native DataGallery.
useDocyrusDataImportWizard
One-call wiring of a Docyrus data source to the native DataImportWizard. Picks a spreadsheet, uploads and analyses it on the server, auto-maps columns, imports the rows and returns a ready-to-render wizard element plus imperative helpers.