# useDocyrusDataViewSelect URL: /docs/web/hooks/use-docyrus-data-view-select Fetch data source fields and saved views from Docyrus and wire them into DataGridViewSelect with a single hook. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-data-view-select ``` **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) - [react-querybuilder](https://react-querybuilder.js.org) This hook is distributed as source. It requires an authenticated `RestApiClient` from `@docyrus/api-client` and a `QueryClientProvider` from `@tanstack/react-query` somewhere above your component tree. ## Overview `useDocyrusDataViewSelect` loads a data source's fields and saved data views from the Docyrus backend via `@docyrus/app-utils`, adapts them to the shapes `DataGridViewSelect` expects, and returns a ready-to-spread props bag. The hook: - Fetches the data source with `expand=enums` so enum-backed fields (status, enum, select, systemEnum) come back with their options attached and are mapped to `FullField.values` for the filter editor. - Wires `create`, `update`, and `delete` to `createDataViewClient` with automatic view-list invalidation. - Tracks the active view as controlled state and persists the last selection per user, scoped by app + data source (+ app id if present). Defaults to `localStorage`; `activeViewStorage: 'session'` scopes it per tab (threaded automatically from `persistState` by the data-listing hooks). ## Usage ```tsx 'use client'; import { useDocyrusDataViewSelect } from '@/hooks/use-docyrus-data-view-select'; import { DataGridViewSelect } from '@/components/docyrus/data-grid-view-select'; import { useReactTable } from '@tanstack/react-table'; export function ContactsGridHeader({ client, table }) { const { gridViewSelectProps, isLoading } = useDocyrusDataViewSelect({ client, appSlug: 'crm', dataSourceSlug: 'contacts' }); if (isLoading) return null; return ( ); } ``` The hook does not accept `table` — spread the returned props onto `DataGridViewSelect` and pass `table` separately so TanStack Table generics are preserved. ## API Reference ### Parameters | Option | Type | Default | Description | |--------|------|---------|-------------| | `client` | `RestApiClient` | — | Authenticated Docyrus API client. | | `appSlug` | `string` | — | Slug of the app the data source belongs to. | | `dataSourceSlug` | `string` | — | Slug of the data source. | | `appId` | `string` | — | Optional app id filter for views scoped to a different app than the data source owner. | | `overrideFields` | `FullField[]` | — | Replace the computed query-builder fields entirely. | | `mapField` | `(field, defaultMapped) => FullField \| null` | — | Per-field transform run after default mapping. Return `null` to drop a field from filter UI. | | `staleTime` | `number` | `30_000` | TanStack Query `staleTime` applied to both the data source and views queries. | | `enabled` | `boolean` | `true` | Disable queries while other state is still loading. | | `dataSource` | `DataSource \| null` | — | Pre-resolved schema. When provided, the hook **skips the `getBySlug` metadata fetch** and reads fields / metadata from this object — the field list, relation targets, and the returned `dataSource` all come from it. See [DB-free metadata](#db-free-metadata). | | `enableDataViews` | `boolean` | `true` | Fetch and manage saved data views (the `/views` endpoint). Set `false` for data sources without a view-configuration backend (e.g. core/tenant system data sources) so the hook never calls `/views`; the tab strip then shows only `systemViews` (if any). | | `dataSourceExpand` | `string \| false` | `'enums'` | `expand` query param for the schema fetch (so select/status fields carry option metadata). Pass `false`/`''` to omit `expand` for backends that don't support it. Ignored when `dataSource` is injected. | | `persistActiveView` | `boolean` | `true` | Persist the last-selected view per user. Set to `false` to disable. | | `persistKey` | `string` | auto | Override the auto-generated storage key (default: `docyrus:data-grid-view::[:]`). | | `activeViewStorage` | `'session' \| 'local'` | `'local'` | Storage backend for the persisted active-view id. The data-listing hooks (`useDocyrusDataGrid` / `useDocyrusDataTable` / `useDocyrusKanban`) thread their `persistState.storage` here automatically, so the active view follows the same session/local scope as the rest of the persisted view parameters. | | `defaultRowGroupingColumn` | `string` | — | Forwarded to `DataGridViewSelect` as the default row-grouping column for views that don't already specify a `grouping`. Pair with [`useDocyrusDataGrid`](/docs/web/hooks/use-docyrus-data-grid) which actually applies it to the table. | ### Return Value | Property | Type | Description | |----------|------|-------------| | `gridViewSelectProps` | `Pick` | Pre-wired props: `views`, `activeViewId`, `fields`, `onViewChange`, `onViewCreate`, `onViewSave`, `onViewDelete`, `disabled`. | | `views` | `SavedDataGridView[]` | Views already mapped from the backend shape. | | `fields` | `FullField[]` | Fields mapped for `react-querybuilder` filter editor. | | `dataSource` | `DataSource \| undefined` | The raw data source metadata response. | | `activeViewId` | `string` | The id of the currently active view (empty while loading). | | `setActiveViewId` | `(viewId: string) => void` | Programmatically switch views. Persisted per user (`activeViewStorage` backend — `localStorage` by default). | | `isLoading` | `boolean` | `true` until both queries have resolved. | | `error` | `Error \| null` | First error from either query. | | `refetch` | `() => void` | Refetch both the data source and views queries. | ## How It Works ### Backend calls - **Data source** (`createDataSourceClient`) — `getBySlug(appSlug, dataSourceSlug, { expand: dataSourceExpand })` loads the data source, its fields, and (with the default `'enums'` expansion) enum options in a single request. The `enums` expansion implies `fields`. Skipped entirely when `dataSource` is injected. - **Views** (`createDataViewClient`) — `list({ appId })` loads non-archived views. Mutations use `create`, `update(id, body)`, and `remove(id)`. Successful mutations invalidate the views query so the tab list refreshes. Skipped when `enableDataViews` is `false`. ### DB-free metadata For surfaces that already hold the schema in memory — or system data sources without a metadata / view-configuration route — the hook can run without any schema fetch: - **`dataSource`** injects the schema; the `getBySlug` call is skipped and fields, relation targets, and the returned `dataSource` all read from your object. - **`enableDataViews: false`** stops the `/views` request (the tab strip then shows only `systemViews`). - **`dataSourceExpand: false`** drops the `expand` param for backends that reject it (only relevant when the hook *does* fetch). ```tsx const viewSelect = useDocyrusDataViewSelect({ client, appSlug: 'core', dataSourceSlug: 'system_users', dataSource: systemUsersSchema, // ← no getBySlug enableDataViews: false // ← no /views }); ``` This is the single integration point that makes [`useDocyrusDataGrid`](/docs/web/hooks/use-docyrus-data-grid), [`useDocyrusDataTable`](/docs/web/hooks/use-docyrus-data-table), [`useDocyrusKanban`](/docs/web/hooks/use-docyrus-kanban), [`useDocyrusDataGallery`](/docs/web/hooks/use-docyrus-data-gallery), and [`useDocyrusMapView`](/docs/web/hooks/use-docyrus-map-view) accept the same `dataSource` / `enableDataViews` / `dataSourceExpand` options — they all forward into this hook. ### View shape mapping `DataGridViewSelect` uses `SavedDataGridView` (TanStack Table + react-querybuilder types). `DataView` from the Docyrus backend uses opaque `Record` fields. The hook packs and unpacks like this: | `DataView` field | `SavedDataGridView` fields | |------------------|----------------------------| | `columns` | `columnVisibility`, `columnOrder`, `columnPinning`, `grouping`, `rowHeight`, `displayMode` | | `filters` | `columnFilters`, `filterQuery` | | `sort` | `sorting` | | `color_rules` | `rowColorRules`, `cellColorRules` | Views created by other clients with empty `columns` still unpack to valid `SavedDataGridView` objects (empty `{}` / `[]` defaults). ### Field type mapping The default `DataSourceField` → `FullField` mapping covers common Docyrus field types (`field-text`, `field-number`, `field-date`, `field-dateTime`, `field-status`, `field-enum`, `field-multiSelect`, etc.). For enum-backed fields, the `enums` array from the `expand=enums` response is mapped to `FullField.values` using each enum's `slug` as the filter value and `name` as the label. For field types that need richer filter UI, pass `mapField` or replace the whole list via `overrideFields`. ### Active view & persistence The hook owns active view state and passes it as a controlled `activeViewId` to `DataGridViewSelect`. On first load: 1. If `persistActiveView` is on (default) and a previous selection is in storage for the same app/data source, that view is restored — provided it still exists. 2. Otherwise the view marked `is_default` on the backend wins. 3. Otherwise the first view wins. User selections update state and write to storage under `docyrus:data-grid-view::[:]`. The backend defaults to `localStorage`; pass `activeViewStorage: 'session'` for per-tab scoping. If the active view is deleted by another client, the hook automatically reselects using the same fallback chain after the next refetch. When a data-listing hook (`useDocyrusDataGrid` / `useDocyrusDataTable` / `useDocyrusKanban`) is given `persistState`, it threads `activeViewStorage` (from `persistState.storage`) and a unified `persistKey` (`docyrus:view-params::[:]:__active-view__` — `appId` included when present because saved-view lists are appId-scoped) into this hook automatically — so the active view lives in the same storage backend and key namespace as the persisted view parameters. Explicit `activeViewStorage` / `persistKey` options passed by the consumer still win. Without `persistState`, the historical always-on `localStorage` behavior is unchanged. The per-user hidden-views list always stays in `localStorage` — hiding a view is a durable preference, not a per-tab tweak. ### Out of scope - Hidden views (`onViewHide` / `onViewUnhide`). `DataView.archived` is a destructive soft-delete and shouldn't be conflated with "hidden from tabs".