Hooks

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.

iOSAndroidExpo Go

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-hooks-use-docyrus-form-view
Required Packages(4 packages)
pnpm add @docyrus/api-client @docyrus/app-utils @tanstack/react-query 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 for the backend-agnostic sibling):

  • Schema through the shared inventory cache (useDocyrusInventory), 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

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 record

Layout

layout is a tree of field slugs, { type: 'field', slug, colSpan } items and sections:

SectionNative renderingKeys
fieldsetBordered panel; with collapsible the title toggles a native Collapsibleid, title, description, items, columns, collapsible, defaultCollapsed, colSpan, className, contentClassName
tabpanelBordered panel with scrollable underline Tabsid, title, description, items: tab[], defaultTabId, colSpan
tabOne 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 (__<slug>_currency, __<slug>_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

PropTypeDefaultDescription
clientRestApiClientrequiredAuthenticated Docyrus API client
appSlugstringrequiredApp slug of the data source
dataSourceSlugstringrequiredData source slug
itemIdstring—Record id — loads the record (edit / view) and is the PATCH target
mode'create' | 'edit' | 'view'itemId ? 'edit' : 'create'Form mode
itemRecord<string, unknown> | null—Pre-loaded record — skips the item fetch
collectionDocyrusFormViewCollection—Generated collection (get / create / update) used instead of the raw items endpoint
dataSourceDataSource | null—Pre-resolved schema — skips the schema fetch
enabledbooleantrueEnable the queries
staleTimenumber30000react-query stale time (ms)
schemaExpandstring | false'enums'expand for the cache-miss schema fetch
defaultValuesRecord<string, unknown>—Extra defaults merged under the record
itemQueryParamsDocyrusFormViewGetParams—Extra params / columns for the record GET
disabledbooleanfalseDisable every field
clickToEditbooleanfalseIn view mode, render through EditableRecordDetail (inline edit + save)
includeReadOnlyFieldsbooleanmode !== '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
gridColumns1 | 2 | 3 | 42Form 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
fieldSlugsstring[]—Whitelist of field slugs to render
fieldOrderstring[]—Explicit field order (unlisted fields sort by name)
hiddenFieldSlugsstring[]—Field slugs to hide
fieldLayoutRecord<string, DocyrusFormViewFieldLayout>—Per-field overrides: hidden / required fns, readOnly, disabled, colSpan, label, description, fieldProps, valueProps, computed*, fieldActions, customValidations, validations
layoutDocyrusFormViewLayoutItem[]—Section tree: fieldset (columns, collapsible, defaultCollapsed) / tabpanel / tab, field items with colSpan
enumOptionsRecord<string, EnumOption[]>—Option lists keyed by field slug (overrides inline enums)
formLayoutRecord<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
resolveUserOptionsbooleantrueFetch /v1/users for user fields
resolveRelationOptionsbooleantrueFetch related records for relation fields
optionLimitnumber100Relation 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
formActionsFormAction[] | null—Form lifecycle actions (onFormLoad / onFormBeforeSubmit / onFormAfterSubmit)
formCustomValidationsFormCustomValidationRule[] | null—Form-level JSONata validations — errors land in formValidationErrors
enumEditorbooleantruePermission-gated "Manage options" footer on enum fields (opens DocyrusEnumOptionEditor)
canManageEnumOptionsboolean—Enum-editor permission override — skips the /v1/users/me fetch
enumEditorAdminRoleIdsstring[]—Role ids allowed to manage options (default ADMIN + ARCHITECT)

Return Value

KeyTypeDescription
modeDocyrusFormViewModeResolved mode
item / valuesRecord<string, unknown>Live form values
defaultValuesRecord<string, unknown>Initial values (field defaults + record)
form{ Field }Render-prop form.Field for custom field UI ({ name, state: { value, meta }, handleChange, handleBlur })
fields / allFieldsDocyrusFormViewField[]Resolved, visible fields (required / hidden / readOnly / disabled / renderMode / enforcedValidations / submitKeys …)
unsupportedFieldsDocyrusFormViewField[]Fields rendered as values because no native editor exists
validationErrorsMap<string, string>Per-field errors from the last validate()
formValidationErrorsstring[]Form-level formCustomValidations errors — render them as an Alert banner
isDirtybooleanValues differ from the baseline (empty values normalized)
isLoadingbooleanData still loading
isSubmittingbooleanA submit is in flight
errorError | nullLoad / submit error
setValue(slug, value) => voidProgrammatic write
validate() => Promise<boolean>required → tokens → JSONata customValidations, then form-level validations
reset() => voidRestore the baseline
submit() => Promise<unknown>Validate → onFormBeforeSubmit → build payload → persist → onFormAfterSubmit
resetActionOverrides() => voidClear field-action and form-action property overrides
renderField(slug, options?) => ReactNodeRender one field (form or value mode)
renderLayout(options?) => ReactNodeRender the whole layout (sections / tabs / grid, or EditableRecordDetail with clickToEdit)
dataSourceDataSource | undefinedResolved schema
columnsstring[]Columns requested for the record
refetch() => voidRefetch schema, record and options

Type Exports

TypeDescription
UseDocyrusFormViewOptionsHook options
UseDocyrusFormViewResultHook result
DocyrusFormViewCollectionGenerated collection shape (get / create / update)
DocyrusFormViewGetParamsRecord GET params
DocyrusFormViewMode / DocyrusFormViewRenderMode / DocyrusFormViewValidationTokenModeMode unions
DocyrusFormViewLayoutItem / DocyrusFormViewSection / DocyrusFormViewFieldsetSection / DocyrusFormViewTabPanelSection / DocyrusFormViewTabSection / DocyrusFormViewLayoutFieldItem / DocyrusFormViewColSpanLayout tree types
DocyrusFormViewFieldLayoutPer-field overrides
DocyrusFormViewFieldResolved field
DocyrusFormViewRenderFieldOptions / DocyrusFormViewRenderLayoutOptionsrenderField / renderLayout options
LocalFormShapeform.Field render-prop shape

On this page