useDocyrusDataViewSelect
Fetch data source fields and saved views from Docyrus and wire them into DataGridViewSelect with a single hook.
Installation
pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-data-view-selectpnpm add @docyrus/app-utils @docyrus/api-client @tanstack/react-query react-querybuilderThis 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=enumsso enum-backed fields (status, enum, select, systemEnum) come back with their options attached and are mapped toFullField.valuesfor the filter editor. - Wires
create,update, anddeletetocreateDataViewClientwith 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 frompersistStateby the data-listing hooks).
Usage
'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 (
<DataGridViewSelect
table={table}
variant="horizontal-tabs"
editable
{...gridViewSelectProps} />
);
}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. |
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:<appSlug>:<dataSourceSlug>[:<appId>]). |
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 which actually applies it to the table. |
Return Value
| Property | Type | Description |
|---|---|---|
gridViewSelectProps | Pick<DataGridViewSelectProps, ...> | 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. Theenumsexpansion impliesfields. Skipped entirely whendataSourceis injected. - Views (
createDataViewClient) —list({ appId })loads non-archived views. Mutations usecreate,update(id, body), andremove(id). Successful mutations invalidate the views query so the tab list refreshes. Skipped whenenableDataViewsisfalse.
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:
dataSourceinjects the schema; thegetBySlugcall is skipped and fields, relation targets, and the returneddataSourceall read from your object.enableDataViews: falsestops the/viewsrequest (the tab strip then shows onlysystemViews).dataSourceExpand: falsedrops theexpandparam for backends that reject it (only relevant when the hook does fetch).
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, useDocyrusDataTable, useDocyrusKanban, useDocyrusDataGallery, and useDocyrusMapView 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<string, unknown> 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:
- If
persistActiveViewis on (default) and a previous selection is in storage for the same app/data source, that view is restored — provided it still exists. - Otherwise the view marked
is_defaulton the backend wins. - Otherwise the first view wins.
User selections update state and write to storage under docyrus:data-grid-view:<appSlug>:<dataSourceSlug>[:<appId>]. 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:<appSlug>:<dataSourceSlug>[:<appId>]:__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.archivedis a destructive soft-delete and shouldn't be conflated with "hidden from tabs".
useDocyrusDataTable
One-call wiring of a Docyrus data source to a fully configured DataTable + toolbar (DataGridViewSelect, search, filters, group, sort) — including row fetching with view-derived query parameters and an optional side-panel filter rail.
useDocyrusDummyDataGeneratorWizard
One-call wiring of a Docyrus data source to the DummyDataGenerator — handles field-aware strategies, deterministic generation, preview, and batch insert in a single guided flow.