# useDocyrusFormView URL: /docs/native/hooks/use-docyrus-form-view Create, edit and view forms bound to a Docyrus data source on React Native. Loads the schema, record and options, uploads files to Docyrus storage and creates or updates the record through the shared form-view engine. ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-form-view ``` **Dependencies:** - [@docyrus/api-client](https://www.npmjs.com/package/@docyrus/api-client) - [@docyrus/app-utils](https://www.npmjs.com/package/@docyrus/app-utils) - [@tanstack/react-query](https://tanstack.com/query/latest) - [jsonata](https://www.npmjs.com/package/jsonata) The native port of the web hook with the same signature. It is a thin Docyrus adapter over the shared form-view engine (see [`useDynamicFormView`](/docs/native/hooks/use-dynamic-form-view) for the backend-agnostic sibling): - **Schema** through the shared inventory cache ([`useDocyrusInventory`](/docs/native/hooks/use-docyrus-inventory)), with a scoped `expand` fetch on a cache miss. Enum options come from the fields' inline `enums`, with a one-off `/v1/apps/enums` fallback when a field has none. - **Record** from `/v1/apps/{app}/data-sources/{ds}/items/{itemId}` (companion columns included; a missing column is stripped and retried). - **Options**: `/v1/users` for user fields, the related data source's items for relation fields. - **Uploads**: image and file fields upload to `/v1/apps/{app}/data-sources/{ds}/files/upload` as an RN `FormData` part (`{ uri, name, type }`) and store the returned `StoredFileValue`. - **Persistence**: `create` → `POST …/items`, `edit` (and inline click-to-edit saves) → `PATCH …/items/{itemId}`, or your `collection` / `onSubmit`. - **Saved forms**: pass `formLayout` (a saved form's `layout`) to derive the layout, per-field overrides, validation tokens, form actions and label styling. It needs an authenticated `RestApiClient` and a `QueryClientProvider` above it. ## Usage ```tsx import { ScrollView } from 'react-native'; import { useDocyrusClient } from '@docyrus/signin/react-native'; import { Button } from '@/components/docyrus-native/button'; import { useDocyrusFormView } from '@/hooks/docyrus-native/use-docyrus-form-view'; export function ContactForm({ contactId }: { contactId?: string }) { const client = useDocyrusClient(); const view = useDocyrusFormView({ client: client!, appSlug: 'base', dataSourceSlug: 'contact', itemId: contactId, validationTokens: 'form', onSubmitSuccess: () => navigation.goBack() }); if (view.isLoading) return null; return ( ); } ``` Read-only detail screen with inline editing: ```tsx const view = useDocyrusFormView({ client, appSlug: 'base', dataSourceSlug: 'contact', itemId, mode: 'view', clickToEdit: true }); return view.renderLayout(); // EditableRecordDetail — each save PATCHes the record ``` ## Layout `layout` is a tree of field slugs, `{ type: 'field', slug, colSpan }` items and sections: | Section | Native rendering | Keys | |---------|------------------|------| | `fieldset` | Bordered panel; with `collapsible` the title toggles a native `Collapsible` | `id`, `title`, `description`, `items`, `columns`, `collapsible`, `defaultCollapsed`, `colSpan`, `className`, `contentClassName` | | `tabpanel` | Bordered panel with scrollable underline `Tabs` | `id`, `title`, `description`, `items: tab[]`, `defaultTabId`, `colSpan` | | `tab` | One tab page (inside a `tabpanel`) | `id`, `title`, `description`, `items`, `className`, `contentClassName` | Fields not placed in the layout are appended after it. The grid is a flex-wrap grid: **phones render one column**, tablets honor `gridColumns` / `columns` (capped at 2 below 900pt) and `colSpan` (`1`–`4` or `'full'`). Sections span the full row unless they carry a `colSpan`. ## Form-level styling `labelAlign` / `labelWidth` / `fieldSize` / `fieldVariant` are resolved into field props (`getFormLayoutFieldProps` in `form-fields/lib/form-layout`) instead of the web descendant CSS selectors (Uniwind has none): `labelAlign: 'left'` → `labelAlignment: 'left'` + a fixed label column (`sm` w-24 / `md` w-40 / `lg` w-56), `fieldSize` → field `size`, `fieldVariant: 'outline' | 'filled'` → `'outlined' | 'filled'`. Explicit `fieldLayout[slug].fieldProps` win. ## Computed fields, actions and validation - **Computed** (`fieldLayout[slug]` or `IField`): `computedHidden` / `computedRequired` (JSONata string or query-builder JSON), `computedLabel` / `computedDescription` (JSONata → string), `computedFormula` (JSONata → value written back into the field). Expressions evaluate against the live values (`quantity * unit_price`). - **Field actions** (`fieldActions`, trigger `onFieldChange`): blocks of IF / ELSE IF (`conditionalItems`) / ELSE (`elseActions`) / ALWAYS (`unconditionalActions`) with the 8 step methods `setFieldValue`, `setFieldValues`, `clearFieldValue`, `showField`, `hideField`, `setFieldRequired`, `setFieldDisabled`, `setFieldReadOnly`. - **Form actions** (`formActions`): the same blocks on `onFormLoad`, `onFormBeforeSubmit` and `onFormAfterSubmit` (`$result` bound). - **Priority**: computed formulas > field-action overrides > form-action overrides > `fieldLayout` callbacks > `IField` defaults. - **Validation order per field**: `required` (always) → tokens (`validationTokens`: `'off'` advisory, `'form'` only `fieldLayout.validations`, `'all'` also the field's own tokens; resolved list on `field.enforcedValidations`, also used for the fields' live hints) → JSONata `customValidations` (`value` + `values` bound). Form-level `formCustomValidations` run once every field passes. - **Payload**: read-only and disabled fields are excluded; companion columns (`___currency`, `___country`, status sub-fields, avatar mapping) are submitted with their field. ## Native differences - Upload handlers receive a `NativeFile` instead of a DOM `File`. - The in-form enum option editor (`enumEditor`, `canManageEnumOptions`, `enumEditorAdminRoleIds`) injects the native `DocyrusEnumOptionEditor` (bottom sheet) as a "Manage options" footer (`ui.enumEditor.manageOptions`) inside the picker sheet of every `field-enum` / `field-systemEnum` field, via `fieldProps.renderOptionsEditor`. Permission is read from `/v1/users/me` (ADMIN / ARCHITECT) unless `canManageEnumOptions` is passed; saving refreshes the schema so the picker shows the new options. ## API Reference | Prop | Type | Default | Description | |------|------|---------|-------------| | `client` | `RestApiClient` | required | Authenticated Docyrus API client | | `appSlug` | `string` | required | App slug of the data source | | `dataSourceSlug` | `string` | required | Data source slug | | `itemId` | `string` | — | Record id — loads the record (edit / view) and is the PATCH target | | `mode` | `'create' \| 'edit' \| 'view'` | `itemId ? 'edit' : 'create'` | Form mode | | `item` | `Record \| null` | — | Pre-loaded record — skips the item fetch | | `collection` | `DocyrusFormViewCollection` | — | Generated collection (get / create / update) used instead of the raw items endpoint | | `dataSource` | `DataSource \| null` | — | Pre-resolved schema — skips the schema fetch | | `enabled` | `boolean` | `true` | Enable the queries | | `staleTime` | `number` | `30000` | react-query stale time (ms) | | `schemaExpand` | `string \| false` | `'enums'` | `expand` for the cache-miss schema fetch | | `defaultValues` | `Record` | — | Extra defaults merged under the record | | `itemQueryParams` | `DocyrusFormViewGetParams` | — | Extra params / columns for the record GET | | `disabled` | `boolean` | `false` | Disable every field | | `clickToEdit` | `boolean` | `false` | In view mode, render through EditableRecordDetail (inline edit + save) | | `includeReadOnlyFields` | `boolean` | `mode !== 'create'` | Render read-only fields as values | | `unsupportedFieldBehavior` | `'skip' \| 'value'` | `create ? 'skip' : 'value'` | Field types without a native editor: skip or render as a value | | `gridColumns` | `1 \| 2 \| 3 \| 4` | `2` | Form grid columns (phones render 1 column; tablets up to 2 below 900pt) | | `validationTokens` | `'off' \| 'form' \| 'all'` | `'off'` | Enforce minLength / maxLength / pattern / min / max tokens on submit (required is always enforced) | | `labelAlign` | `'top' \| 'left'` | `'top'` | Form-level label placement | | `labelWidth` | `'sm' \| 'md' \| 'lg'` | `'md'` | Label column width when labelAlign is left | | `fieldSize` | `'sm' \| 'md' \| 'lg'` | — | Form-level field density | | `fieldVariant` | `'outline' \| 'filled'` | — | Form-level input style | | `fieldSlugs` | `string[]` | — | Whitelist of field slugs to render | | `fieldOrder` | `string[]` | — | Explicit field order (unlisted fields sort by name) | | `hiddenFieldSlugs` | `string[]` | — | Field slugs to hide | | `fieldLayout` | `Record` | — | Per-field overrides: hidden / required fns, readOnly, disabled, colSpan, label, description, fieldProps, valueProps, computed*, fieldActions, customValidations, validations | | `layout` | `DocyrusFormViewLayoutItem[]` | — | Section tree: fieldset (columns, collapsible, defaultCollapsed) / tabpanel / tab, field items with colSpan | | `enumOptions` | `Record` | — | Option lists keyed by field slug (overrides inline enums) | | `formLayout` | `Record \| null` | — | Saved Docyrus form layout — converted into layout / fieldLayout / fieldSlugs / columns | | `mapField` | `(field: DataSourceField, mapped: IField) => IField \| null` | — | Customize (or drop) each mapped field | | `dynamicLabelTranslator` | `(label: string) => string` | — | Translate field labels | | `dynamicEnumOptionTranslator` | `(option: EnumOption, field: IField) => string` | — | Translate enum option labels | | `resolveUserOptions` | `boolean` | `true` | Fetch /v1/users for user fields | | `resolveRelationOptions` | `boolean` | `true` | Fetch related records for relation fields | | `optionLimit` | `number` | `100` | Relation option window size | | `onSubmit` | `(payload, context) => unknown` | — | Custom persistence — default: POST (create) / PATCH (edit) the items endpoint | | `transformSubmit` | `(payload, context) => Record` | — | Rewrite the built payload before persistence | | `onSubmitSuccess` | `(result, payload) => void` | — | Called after a successful submit | | `onSubmitError` | `(error, payload) => void` | — | Called when validation or persistence fails | | `formActions` | `FormAction[] \| null` | — | Form lifecycle actions (onFormLoad / onFormBeforeSubmit / onFormAfterSubmit) | | `formCustomValidations` | `FormCustomValidationRule[] \| null` | — | Form-level JSONata validations — errors land in formValidationErrors | | `enumEditor` | `boolean` | `true` | Permission-gated "Manage options" footer on enum fields (opens `DocyrusEnumOptionEditor`) | | `canManageEnumOptions` | `boolean` | — | Enum-editor permission override — skips the `/v1/users/me` fetch | | `enumEditorAdminRoleIds` | `string[]` | — | Role ids allowed to manage options (default ADMIN + ARCHITECT) | ## Return Value | Key | Type | Description | |-----|------|-------------| | `mode` | `DocyrusFormViewMode` | Resolved mode | | `item` / `values` | `Record` | Live form values | | `defaultValues` | `Record` | Initial values (field defaults + record) | | `form` | `{ Field }` | Render-prop `form.Field` for custom field UI (`{ name, state: { value, meta }, handleChange, handleBlur }`) | | `fields` / `allFields` | `DocyrusFormViewField[]` | Resolved, visible fields (required / hidden / readOnly / disabled / renderMode / enforcedValidations / submitKeys …) | | `unsupportedFields` | `DocyrusFormViewField[]` | Fields rendered as values because no native editor exists | | `validationErrors` | `Map` | Per-field errors from the last `validate()` | | `formValidationErrors` | `string[]` | Form-level `formCustomValidations` errors — render them as an `Alert` banner | | `isDirty` | `boolean` | Values differ from the baseline (empty values normalized) | | `isLoading` | `boolean` | Data still loading | | `isSubmitting` | `boolean` | A submit is in flight | | `error` | `Error \| null` | Load / submit error | | `setValue` | `(slug, value) => void` | Programmatic write | | `validate` | `() => Promise` | required → tokens → JSONata `customValidations`, then form-level validations | | `reset` | `() => void` | Restore the baseline | | `submit` | `() => Promise` | Validate → `onFormBeforeSubmit` → build payload → persist → `onFormAfterSubmit` | | `resetActionOverrides` | `() => void` | Clear field-action and form-action property overrides | | `renderField` | `(slug, options?) => ReactNode` | Render one field (form or value mode) | | `renderLayout` | `(options?) => ReactNode` | Render the whole layout (sections / tabs / grid, or `EditableRecordDetail` with `clickToEdit`) | | `dataSource` | `DataSource \| undefined` | Resolved schema | | `columns` | `string[]` | Columns requested for the record | | `refetch` | `() => void` | Refetch schema, record and options | ## Type Exports | Type | Description | |------|-------------| | `UseDocyrusFormViewOptions` | Hook options | | `UseDocyrusFormViewResult` | Hook result | | `DocyrusFormViewCollection` | Generated collection shape (`get` / `create` / `update`) | | `DocyrusFormViewGetParams` | Record GET params | | `DocyrusFormViewMode` / `DocyrusFormViewRenderMode` / `DocyrusFormViewValidationTokenMode` | Mode unions | | `DocyrusFormViewLayoutItem` / `DocyrusFormViewSection` / `DocyrusFormViewFieldsetSection` / `DocyrusFormViewTabPanelSection` / `DocyrusFormViewTabSection` / `DocyrusFormViewLayoutFieldItem` / `DocyrusFormViewColSpan` | Layout tree types | | `DocyrusFormViewFieldLayout` | Per-field overrides | | `DocyrusFormViewField` | Resolved field | | `DocyrusFormViewRenderFieldOptions` / `DocyrusFormViewRenderLayoutOptions` | `renderField` / `renderLayout` options | | `LocalFormShape` | `form.Field` render-prop shape |