useDocyrusFormView
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
pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-form-viewpnpm add @docyrus/api-client @docyrus/app-utils @tanstack/react-query jsonataThe native port of the web hook with the same signature. It is a thin Docyrus adapter over the shared form-view engine (see useDynamicFormView for the backend-agnostic sibling):
- Schema through the shared inventory cache (
useDocyrusInventory), with a scopedexpandfetch on a cache miss. Enum options come from the fields' inlineenums, with a one-off/v1/apps/enumsfallback 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/usersfor 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/uploadas an RNFormDatapart ({ uri, name, type }) and store the returnedStoredFileValue. - Persistence:
create→POST …/items,edit(and inline click-to-edit saves) →PATCH …/items/{itemId}, or yourcollection/onSubmit. - Saved forms: pass
formLayout(a saved form'slayout) 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
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 (
<ScrollView contentContainerClassName="gap-4 p-4">
{view.renderLayout()}
<Button onPress={() => view.submit()} loading={view.isSubmitting}>Save</Button>
</ScrollView>
);
}Read-only detail screen with inline editing:
const view = useDocyrusFormView({ client, appSlug: 'base', dataSourceSlug: 'contact', itemId, mode: 'view', clickToEdit: true });
return view.renderLayout(); // EditableRecordDetail — each save PATCHes the recordLayout
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]orIField):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, triggeronFieldChange): blocks of IF / ELSE IF (conditionalItems) / ELSE (elseActions) / ALWAYS (unconditionalActions) with the 8 step methodssetFieldValue,setFieldValues,clearFieldValue,showField,hideField,setFieldRequired,setFieldDisabled,setFieldReadOnly. - Form actions (
formActions): the same blocks ononFormLoad,onFormBeforeSubmitandonFormAfterSubmit($resultbound). - Priority: computed formulas > field-action overrides > form-action overrides >
fieldLayoutcallbacks >IFielddefaults. - Validation order per field:
required(always) → tokens (validationTokens:'off'advisory,'form'onlyfieldLayout.validations,'all'also the field's own tokens; resolved list onfield.enforcedValidations, also used for the fields' live hints) → JSONatacustomValidations(value+valuesbound). Form-levelformCustomValidationsrun once every field passes. - Payload: read-only and disabled fields are excluded; companion columns (
__<slug>_currency,__<slug>_country, status sub-fields, avatar mapping) are submitted with their field.
Native differences
- Upload handlers receive a
NativeFileinstead of a DOMFile. - The in-form enum option editor (
enumEditor,canManageEnumOptions,enumEditorAdminRoleIds) injects the nativeDocyrusEnumOptionEditor(bottom sheet) as a "Manage options" footer (ui.enumEditor.manageOptions) inside the picker sheet of everyfield-enum/field-systemEnumfield, viafieldProps.renderOptionsEditor. Permission is read from/v1/users/me(ADMIN / ARCHITECT) unlesscanManageEnumOptionsis 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<string, unknown> | 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<string, unknown> | — | 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<string, DocyrusFormViewFieldLayout> | — | 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<string, EnumOption[]> | — | Option lists keyed by field slug (overrides inline enums) |
formLayout | Record<string, unknown> | 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<string, unknown> | — | 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<string, unknown> | Live form values |
defaultValues | Record<string, unknown> | 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<string, string> | 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<boolean> | required → tokens → JSONata customValidations, then form-level validations |
reset | () => void | Restore the baseline |
submit | () => Promise<unknown> | 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 |
useDocyrusFieldComponent
Resolve the native form field, value renderer or inline editor for a Docyrus field type — plus the shared FORM_FIELD_MAP / VALUE_RENDERER_MAP registries and IField helpers.
useDocyrusInstantMessageComposer
Wires the native InstantMessageComposer to the Docyrus SMS and WhatsApp messaging API.