useDocyrusFieldComponent
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
pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-field-componentThis 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 returnsEditableValue, which dispatches read/edit modes by field type internally.tanstack-column-def— Returns a<TData>(opts) => ColumnDef<TData>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
'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<string, unknown>;
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 <FormField field={field} form={form} />;
// value-renderer always returns a component (TextValue fallback)
return <Value field={field} value={value} record={record} />;
}The return type is conditionally typed by the kind argument:
const Form = useDocyrusFieldComponent('field-select', 'form-field');
// ^? ComponentType<DocyrusFormFieldProps> | null
const Value = useDocyrusFieldComponent('field-select', 'value-renderer');
// ^? ComponentType<DocyrusValueProps>
const Cell = useDocyrusFieldComponent('field-select', 'data-grid-cell-variant');
// ^? ComponentType<DataGridCellProps<unknown>>
const Editable = useDocyrusFieldComponent('field-select', 'editable-value');
// ^? typeof EditableValue
const Build = useDocyrusFieldComponent('field-select', 'tanstack-column-def');
// ^? <TData>(opts: BuildTanstackColumnDefOptions) => ColumnDef<TData>Building TanStack column defs
For dynamic tables, get a column-def builder for each field and feed the results to TanStack Table:
'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<TData> with:
id=field.slugaccessorFnreadsrow[field.slug]and normalizes object payloads (enum/multi/relation) so grouping & sorting workheader=field.namemeta.label,meta.cell(theCellOptsconfig),meta.groupable, andmeta.renderGroupValue(renders group headers with the right value renderer)
The
fieldTypeargument is a memoization key for this kind — the builder itself readsfield.typefrom the passedfield, so a single builder can construct columns for any field type. Pass anyIFieldType; 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<DocyrusFormFieldProps> | null | null (read-only/unsupported types) |
value-renderer | ComponentType<DocyrusValueProps> | TextValue |
data-grid-cell-variant | ComponentType<DataGridCellProps<unknown>> | ShortTextCell |
editable-value | typeof EditableValue | EditableValue (it dispatches by field type internally) |
tanstack-column-def | <TData>(opts: BuildTanstackColumnDefOptions) => ColumnDef<TData> | 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<Record<IFieldType, ComponentType<DocyrusFormFieldProps>>> | Field type → form input component. |
VALUE_RENDERER_MAP | Partial<Record<IFieldType, ComponentType<DocyrusValueProps>>> | Field type → read-only renderer. |
CELL_COMPONENT_MAP | Partial<Record<IFieldType, ComponentType<DataGridCellProps<unknown>>>> | Field type → data-grid cell component. |
GROUPABLE_FIELD_TYPES | Set<IFieldType> | Field types selectable in the grouping picker. |
getCellOpts(field, opts) | (field, { appSlug?, dataSourceSlug? }) => CellOpts | Pure function: field metadata → TanStack meta.cell config. |
buildTanstackColumnDef(opts) | <TData>(opts) => ColumnDef<TData> | 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, 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 <Editable field={...} value={...} ... /> 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
useDocyrusDataGridwhich callsbuildTanstackColumnDefinternally.
useDocyrusEmailComposer
Wire an `<EmailComposer />` to the Docyrus messaging API — loads sender accounts, lets the user pick a From address, and ships emails through `/v1/messaging/email/accounts/{id}/send`.
useDocyrusFormView
One-call wiring of a Docyrus data source item to create, edit, and read-only layouts with shared field-component mapping, item loading, option hydration, and submit handling.