# useDocyrusFieldComponent URL: /docs/web/hooks/use-docyrus-field-component Resolve the right UI component (form input, value renderer, data-grid cell, editable value, or TanStack column def builder) for any Docyrus data source field type. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-field-component ``` This hook is distributed as source. It re-exports component registries used by `DynamicFormField`, `DynamicValue`, the data-grid cell system, and `useDocyrusDataGrid`, so adding a new field type only requires updating one place. ## Overview `useDocyrusFieldComponent` is a synchronous lookup hook that maps a Docyrus field type (e.g. `field-select`) to the right React component (or builder) for one of five render contexts: - **`form-field`** — TanStack Form input component (e.g. `SelectFormField`). - **`value-renderer`** — Read-only display component (e.g. `SelectValue`). - **`data-grid-cell-variant`** — TanStack-table cell component (e.g. `SelectCell`). - **`editable-value`** — Always returns `EditableValue`, which dispatches read/edit modes by field type internally. - **`tanstack-column-def`** — Returns a `(opts) => ColumnDef` builder that produces a fully-configured TanStack column for the field. With this hook, you can build fully dynamic interfaces (auto-rendered forms, tables, and inline-edit views) just by iterating field metadata — no per-type switch statements at the call site. ## Usage ```tsx 'use client'; import { useDocyrusFieldComponent } from '@/hooks/use-docyrus-field-component'; import { type IField } from '@/components/docyrus/form-fields/types'; export function DynamicFieldRenderer({ field, value, record, form }: { field: IField; value: unknown; record: Record; form: any; }) { const FormField = useDocyrusFieldComponent(field.type, 'form-field'); const Value = useDocyrusFieldComponent(field.type, 'value-renderer'); const Cell = useDocyrusFieldComponent(field.type, 'data-grid-cell-variant'); const Editable = useDocyrusFieldComponent(field.type, 'editable-value'); // form-field can be null for read-only/unsupported types if (FormField) return ; // value-renderer always returns a component (TextValue fallback) return ; } ``` The return type is conditionally typed by the `kind` argument: ```ts const Form = useDocyrusFieldComponent('field-select', 'form-field'); // ^? ComponentType | null const Value = useDocyrusFieldComponent('field-select', 'value-renderer'); // ^? ComponentType const Cell = useDocyrusFieldComponent('field-select', 'data-grid-cell-variant'); // ^? ComponentType> const Editable = useDocyrusFieldComponent('field-select', 'editable-value'); // ^? typeof EditableValue const Build = useDocyrusFieldComponent('field-select', 'tanstack-column-def'); // ^? (opts: BuildTanstackColumnDefOptions) => ColumnDef ``` ### Building TanStack column defs For dynamic tables, get a column-def builder for each field and feed the results to TanStack Table: ```tsx 'use client'; import { useMemo } from 'react'; import { useDocyrusFieldComponent } from '@/hooks/use-docyrus-field-component'; import { useReactTable, getCoreRowModel } from '@tanstack/react-table'; export function ContactsTable({ fields, rows, appSlug, dataSourceSlug }) { const buildColumn = useDocyrusFieldComponent(fields[0]?.type, 'tanstack-column-def'); const columns = useMemo( () => fields.map((field) => buildColumn({ field, appSlug, dataSourceSlug })), [fields, buildColumn, appSlug, dataSourceSlug] ); const table = useReactTable({ data: rows, columns, getCoreRowModel: getCoreRowModel() }); // ... } ``` The builder returns `ColumnDef` with: - `id` = `field.slug` - `accessorFn` reads `row[field.slug]` and normalizes object payloads (enum/multi/relation) so grouping & sorting work - `header` = `field.name` - `meta.label`, `meta.cell` (the `CellOpts` config), `meta.groupable`, and `meta.renderGroupValue` (renders group headers with the right value renderer) > The `fieldType` argument is a memoization key for this kind — the builder itself reads `field.type` from the passed `field`, so a single builder can construct columns for any field type. Pass any `IFieldType`; we recommend the type of one representative field for stability. ## API Reference ### Parameters | Parameter | Type | Description | |-----------|------|-------------| | `fieldType` | `IFieldType` | The Docyrus data source field type, e.g. `field-text`, `field-select`, `field-dateTime`. | | `kind` | `'form-field' \| 'value-renderer' \| 'data-grid-cell-variant' \| 'editable-value' \| 'tanstack-column-def'` | Which render context to resolve the component for. | ### Return Value | `kind` | Returns | Unknown-type fallback | |--------|---------|-----------------------| | `form-field` | `ComponentType \| null` | `null` (read-only/unsupported types) | | `value-renderer` | `ComponentType` | `TextValue` | | `data-grid-cell-variant` | `ComponentType>` | `ShortTextCell` | | `editable-value` | `typeof EditableValue` | `EditableValue` (it dispatches by field type internally) | | `tanstack-column-def` | `(opts: BuildTanstackColumnDefOptions) => ColumnDef` | Builder produces `short-text` cell variant for unknown types | ### Exported registries The hook module exports the underlying maps and helpers as named consts so non-React code, server components, or column-def builders can read them directly: | Export | Type | Purpose | |--------|------|---------| | `FORM_FIELD_MAP` | `Partial>>` | Field type → form input component. | | `VALUE_RENDERER_MAP` | `Partial>>` | Field type → read-only renderer. | | `CELL_COMPONENT_MAP` | `Partial>>>` | Field type → data-grid cell component. | | `GROUPABLE_FIELD_TYPES` | `Set` | Field types selectable in the grouping picker. | | `getCellOpts(field, opts)` | `(field, { appSlug?, dataSourceSlug? }) => CellOpts` | Pure function: field metadata → TanStack `meta.cell` config. | | `buildTanstackColumnDef(opts)` | `(opts) => ColumnDef` | Pure builder: field metadata → full `ColumnDef`. | `DynamicFormField`, `DynamicValue`, and `useDocyrusDataGrid` all consume this hook internally — adding a new field type means editing the maps in this module and nothing else. ### `BuildTanstackColumnDefOptions` | Field | Type | Description | |-------|------|-------------| | `field` | `DocyrusFieldLike` | Field metadata. Accepts both `IField` and `@docyrus/app-utils`'s `DataSourceField`. | | `appSlug` | `string` | Wired into `enum` cell meta for dynamic option loading. | | `dataSourceSlug` | `string` | Wired into `enum` cell meta for dynamic option loading. | ## How It Works The hook is a memoized lookup with no state, no effects, and no fetching. It reads from one of the three exported registries based on `kind`, applies the appropriate fallback for unknown field types, and returns the resolved component (referentially stable while `fieldType` and `kind` are unchanged). ### Why `data-grid-cell-variant` returns the cell component, not the variant string The cell components (`SelectCell`, `EnumCell`, etc.) render correctly only when their host column is wired with the matching `meta.cell` config (options, app/data source slugs, etc.). For TanStack column-def construction with that wiring, use [`useDocyrusDataGrid`](/docs/web/hooks/use-docyrus-data-grid), which builds the variant **and** the column meta together. This hook is the lower-level component-resolution primitive. ### Why `editable-value` always returns `EditableValue` `EditableValue` is itself a field-type dispatcher: internal sets (`INLINE_TYPES`, `INSTANT_SAVE_TYPES`, `EXPLICIT_SAVE_TYPES`, `POPOVER_TYPES`, `READ_ONLY_TYPES`) drive its read/edit behavior per field type. Returning the same component for every field type keeps the hook's API consistent (always returns a renderable component) and lets callers always render `` regardless of type. ### Out of scope - Fetching enum options or relation records — pass them as props (`enumOptions`, etc.) when rendering the returned component. - Wiring up the full data-grid (rows, view tabs, toolbar) — use [`useDocyrusDataGrid`](/docs/web/hooks/use-docyrus-data-grid) which calls `buildTanstackColumnDef` internally.