Hooks

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.

iOSAndroidExpo Go

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-data-grid
Required Packages(5 packages)
pnpm add @docyrus/app-utils @docyrus/api-client @tanstack/react-query @tanstack/react-table @react-querybuilder/core

A 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-readOnly fields locked.
  • Items query — GET /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/items (or collection.list) with columns (relations projected as slug(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), debounced filterKeyword, and limit / offset / fullCount in standard paging. A phantom column reported by the backend is stripped and the request retried.
  • Saved views — gridViewSelectProps drives the native DataGridViewSelect; the active view is applied to the table (visibility, order, pinning with select / actions kept first, sort, grouping, row height, paging, inline editing, color rules).
  • Advanced filter — DataGridAdvancedFilter stores 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 (or collection.updateMany); status cells post to …/status-update/{field} through tableMeta.onStatusUpdate.
  • Bulk actions — update / delete / export / email / message on the selection bar (see Native deltas).
  • Gallery mode — per-view galleryCardConfig / galleryDisplayConfig (saved view → cardConfig option → detectCardConfigFromFields), editable from the toolbar gallery menus and the view editor.
  • Form binding — activeViewFormId / activeViewForm / formViewProps for a companion useDocyrusFormView.
  • Persistence — persistState snapshots 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

OptionTypeDefaultDescription
dataTData[]—Pre-resolved rows; skips the items query (search then filters client-side).
collectionDocyrusDataGridCollection<TData>—{ list, updateMany?, deleteMany? } — drives fetch, inline save and bulk delete.
listParamsDocyrusDataGridListParams—Extra items params; filters is AND-merged, every other key overrides.
pivotFiltersDocyrusPivotFilterGroupField[]—Pivot strips stacked on top of toolbar; their rule is ANDed into the query and they cross-filter against the grid's filters.
defaultLimitnumber100Page size when neither the view nor listParams set one.
enableItemsQuerybooleandata === undefinedToggle the internal items query.
defaultPagingMode'standard' | 'virtual-scroll''virtual-scroll'Paging mode when the view carries none.
excludeFieldSlugsstring[]—Slugs dropped from the schema before anything uses it.
fieldEnumsRecord<string, { id; name; color? }[]>—Enum options for fields the API returns without any.
enableRelationColumnsbooleanfalseOffer columns borrowed from related data sources.
relationIconFieldsRecord<string, string>—Relation slug → icon / image field on the related data source (projected and shown in the cell).
usersCellUserOption[]—Tenant users for user cells and the user filter (filtered client-side).
persistStateboolean | { storage?: 'session' | 'local'; key?: string }—Persist manually edited view parameters per active view (true → in-memory session store).

Columns

OptionTypeDefaultDescription
showSelectColumnbooleantrueLeading select column (pinned left).
enableRowMarkersbooleantrueRow numbers on the select column.
selectColumnColumnDef<TData>—Replace the select column.
actionsColumnColumnDef<TData>—Column after the select column (pinned left).
extraColumnsColumnDef<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)

OptionTypeDefaultDescription
readOnlybooleantrueEditing is also switched on by a view's inlineEditingEnabled.
trackChangesbooleantrueSave / discard bar (forced on while inline editing).
onSaveChanges(changes: RowChange[], data: TData[]) => void | Promise<void>bulk PATCHCustom save handler.
enableSearchbooleanfalseGrid in-cell search.
enableGroupingbooleantrueRow grouping.
rowColorRules / cellColorRulesDataGridRowColorRule[] / DataGridCellColorRule[]—Color rules.
getRowLabel(row, rowIndex) => string—Row label (a11y + save bar).
onRowAdd / onRowsAdd / onRowsDelete / onDataChangeuseDataGrid handlers—Forwarded.
metaTableMeta<TData>—Extra table meta; the hook's own wiring wins.
initialStateInitialTableState—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 / formatNumberformatterscontextExplicit formatters; default to DateFormatProvider / NumberFormatProvider when a real provider is mounted.

Toolbar

OptionTypeDefaultDescription
enableViewSelectbooleantrueView picker.
viewSelectVariantDataGridViewSelectVariant'horizontal-tabs'Picker variant ('vertical-tabs' has no native side panel — the picker is omitted).
viewSelectMaxVisiblenumber—Max inline tabs.
enableSearchInputbooleantrueSearch input (debounced into filterKeyword).
searchPlaceholderstringt('ui.common.search', 'Search…')Placeholder.
searchDebounceMsnumber300Search debounce.
enableFilterMenubooleantrueFilter chips + (server-driven grids) the advanced filter.
enableGroupMenu / enableSortMenu / enableRowHeightMenu / enableFieldsMenu / enableDisplayMenubooleantrueMenu triggers.
enableGalleryMenusbooleantrueCard-fields + display menus in gallery mode.
enableServerExportMenubooleantrueServer export (POST /v1/edge/run/query-export, written + shared).
enableClientExportMenubooleanfalseClient export of loaded rows (only when the server export is off).
serverExportLimitnumber10000Server export row cap.
serverExportExcludedFieldTypes / serverExportExcludedSlugsstring[]internal listsServer export exclusions.
enableReloadButtonbooleantrueRecords-only reload.
onReload() => void—After a reload.
toolbarClassNamestring—Toolbar root classes.
toolbarStartContent / toolbarEndContentReactNode—Custom toolbar content.

Bulk actions & compose

OptionTypeDefaultDescription
bulkActionsfalse | DocyrusDataGridBulkAction[]['update', 'delete', 'export', 'email', 'message']Built-in selection actions (email / message schema-gated).
extraBulkActionsDataGridAction<TData>[]—Appended selection actions.
exportColumns'visible' | 'all' | string[]'visible'Columns of the bulk / client export.
exportFileNamestringdataSourceSlugExport file name.
renderBulkUpdateDialog(props: DocyrusBulkUpdateDialogRenderProps) => ReactNodeBulkUpdateDialogNative. Replace the default bulk-update dialog.
renderEmailComposeDialog(props: DocyrusEmailComposeDialogRenderProps) => ReactNodeEmailComposeDialogNative. Replace the composer for the email action and email cells.
renderMessageComposeDialog(props: DocyrusMessageComposeDialogRenderProps) => ReactNodeInstantMessageComposeDialogNative. 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

OptionTypeDefaultDescription
enableSideFiltersbooleanfalseRender sideFilters and AND its rule group into the items query. Needs sideFiltersConfig.
sideFiltersConfigDocyrusDataGridSideFiltersConfig<TData>—Panel configuration (below).
sideFiltersDefaultExpandedbooleantrueInitial expanded state of the inline panel.
sideFiltersExpanded / onSideFiltersExpandedChangeboolean / (expanded) => void—Controlled expanded state (inline).
sideFiltersWidthnumber | string280Width of the expanded inline panel; ignored for the sheet.

DocyrusDataGridSideFiltersConfig<TData>:

FieldTypeDefaultDescription
columnsConfigColumnConfig<TData>[]—Filter columns (required).
strategy'server' | 'client''server'Filter strategy.
defaultsSideFilterDefaults—Per-column UI hints.
sectionsSideFilterSectionGroup[]—Named section groups.
variant'default' | 'bordered' | 'compact''default'Panel variant.
titleReactNode'Filters'Header title (null hides it).
showActiveChips / showClearAllbooleantrueActive chips / "Clear all".
searchableboolean | string—Search input (column id drives a specific column).
clearAllLabel / clearLabelstring—Label overrides.
localeLocale'en'Filter UI locale.
classNamestring—Panel classes.
collapseAriaLabel / expandAriaLabelstring—Inline collapse / expand labels.
collapsedWidthnumber | string—Accepted for parity (collapsed bar is full-width).
presentation'sheet' | 'inline''sheet'Native. Trigger + bottom sheet, or the inline collapsible panel.
triggerLabelstringtitle → 'Filters'Native. Sheet trigger label.
OptionTypeDefaultDescription
cardConfigDataGalleryCardConfig<TData>auto-detectedInitial card bindings + render hooks.
galleryDisplayConfigPartial<DataGalleryDisplayConfig>defaultsInitial display config.
onCardClick(record, rowIndex) => void—Card tap.
defaultFormLayoutRecord<string, unknown> | null—Form layout when the view has no bound form.
enableColumnReorder, showDropdownChevron, enablePasteboolean—Desktop affordances, accepted and ignored.

Result (UseDocyrusDataGridResult<TData>)

Everything useDocyrusDataViewSelect returns (gridViewSelectProps wraps the gallery config into view save / create; fields honour excludeFieldSlugs), plus:

FieldTypeDescription
tableTable<TData>TanStack table — <DataGrid table> and the menus.
gridPropsobjectSpread onto <DataGrid>: the useDataGrid result + actions, isReloading, isLoading, onClearFilters, onRefresh (pull-to-refresh), galleryCardConfig, galleryDisplayConfig, onCardClick.
toolbarReactNodePivot strips → view picker → search → scrollable menu row, plus the bulk dialogs.
pivotFilterRulePivotFilterRule[] | nullCombined pivot rule.
pivotFiltersStripReactNodeThe pivot strip alone (null without pivotFilters).
selectedRows / selectedRowCountTData[] / numberReactive selection.
activeViewFormId / activeViewFormstring | undefined / DataForm | undefinedForm bound to the active view.
formViewPropsDocyrusDataGridFormViewProps{ formLayout, gridColumns?, dataSource } for useDocyrusFormView.
itemsTData[]Rows passed to the grid.
resolvedListParamsDocyrusDataGridListParamsParams actually sent.
pagingMode'standard' | 'virtual-scroll' | undefinedPass to <DataGrid pagingMode>.
reload() => voidSchema + views + items refetch, then onReload.
sidePanelReactNodeDataGridSidePanel with the vertical-tabs view picker when viewSelectVariant === 'vertical-tabs', else null.
sideFiltersReactNodeDataTableSideFilters (sheet trigger by default), null unless enableSideFilters + config.
sideFiltersExpanded / setSideFiltersExpandedboolean / (v) => voidInline panel expanded state.
sideFiltersQueryRuleGroupType | undefinedRule 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

ExportDescription
useDocyrusDataGridThe 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_TYPESSet(['field-email']) / Set(['field-phone']).

Type Exports

TypeDescription
UseDocyrusDataGridOptions / UseDocyrusDataGridResultOptions / result.
DocyrusDataGridListParamsItems query params.
DocyrusDataGridCollection{ list, updateMany?, deleteMany? }.
DocyrusDataGridBulkAction'update' | 'delete' | 'export' | 'email' | 'message'.
DocyrusDataGridFormViewPropsformViewProps shape.
DocyrusDataGridSideFiltersConfigSide-filter config (see Side filters).
DataGridPersistSnapshotPersisted parameter snapshot.
DocyrusBulkUpdateDialogRenderPropsrenderBulkUpdateDialog props (open, onOpenChange, client, appSlug, dataSourceSlug, records, onSuccess).
DocyrusEmailComposeDialogRenderProps / DocyrusMessageComposeDialogRenderPropsCompose slot props (client, trigger: null, open, onOpenChange, to).

On this page