Skip to content

useDataForm

The main React hook for schema-driven forms. It manages field state, applies events and handlers, resolves groups and scoped actions, and returns proxied fields for dynamic overrides.

Basic Usage

tsx
import { useDataForm, getRenderer } from '@anhanga/react'
import { Scope } from '@anhanga/core'

function PersonForm() {
  const form = useDataForm({
    schema: PersonSchema.provide(),
    scope: Scope.add,
    events: personEvents,
    handlers: personHandlers,
    component: componentContract,
    translate: t,
  })

  return (
    <form>
      {form.fields.map((field) => {
        const props = form.getFieldProps(field.name)
        const Renderer = getRenderer(field.config.component)
        return <Renderer key={field.name} {...props} />
      })}

      {form.actions.map((action) => (
        <button key={action.name} onClick={action.execute}>
          {action.label}
        </button>
      ))}
    </form>
  )
}

Options

typescript
interface UseDataFormOptions {
  schema: SchemaProvide
  scope: ScopeValue
  events?: Record<string, Record<string, EventFn>>
  handlers?: Record<string, HandlerFn>
  hooks?: SchemaHooks
  context?: Record<string, unknown>
  component: ComponentContract
  initialValues?: Record<string, unknown>
  translate?: TranslateContract
}
OptionRequiredDescription
schemaYesOutput of SchemaDefinition.provide()
scopeYesCurrent scope (Scope.add, Scope.edit, etc.)
eventsNoField event handlers from SchemaDefinition.events()
handlersNoAction handlers from SchemaDefinition.handlers()
hooksNoLifecycle hooks from SchemaDefinition.hooks()
contextNoContextual data (e.g., route params with record ID)
componentYesComponentContract implementation
initialValuesNoInitial field values
translateNoTranslation function

Return Value

typescript
interface UseDataFormReturn {
  loading: boolean
  state: Record<string, unknown>
  fields: ResolvedField[]
  groups: FieldGroup[]
  ungrouped: ResolvedField[]
  sections: FormSection[]
  actions: ResolvedAction[]
  errors: Record<string, string[]>
  dirty: boolean
  valid: boolean
  setValue(field: string, value: unknown): void
  setValues(values: Record<string, unknown>): void
  reset(values?: Record<string, unknown>): void
  validate(): boolean
  getFieldProps(name: string): FieldRendererProps
}

Properties

PropertyTypeDescription
loadingbooleanTrue while bootstrap hooks are running
stateRecord<string, unknown>Current form values
fieldsResolvedField[]Fields filtered by current scope
groupsFieldGroup[]Groups with their resolved fields
ungroupedResolvedField[]Fields not assigned to any group
sectionsFormSection[]Interleaved groups and ungrouped fields
actionsResolvedAction[]Actions filtered by current scope
errorsRecord<string, string[]>Validation errors per field
dirtybooleanTrue if any field has changed
validbooleanTrue if all validations pass

Methods

MethodDescription
setValue(field, value)Set a single field value
setValues(values)Set multiple field values
reset(values?)Reset form to initial or provided values
validate()Run all validations, returns boolean
getFieldProps(name)Get FieldRendererProps for a field

FieldRendererProps

getFieldProps() returns everything a field renderer needs:

typescript
interface FieldRendererProps {
  domain: string
  name: string
  value: unknown
  config: FieldConfig
  proxy: FieldProxy
  errors: string[]
  scope: ScopeValue
  onChange(value: unknown): void
  onBlur(): void
  onFocus(): void
}
PropDescription
domainSchema domain name
nameField name
valueCurrent field value
configField configuration (component, kind, validations, etc.)
proxyDynamic overrides (hidden, disabled, width, height, state)
errorsValidation error messages
scopeCurrent scope
onChangeValue change callback (triggers change events)
onBlurBlur callback (triggers blur events)
onFocusFocus callback (triggers focus events)

Rendering with Sections

tsx
function PersonForm() {
  const form = useDataForm({ /* ... */ })

  return (
    <form>
      {form.sections.map((section, i) => {
        if (section.kind === 'group') {
          return (
            <fieldset key={section.name}>
              <legend>{t(`person.groups.${section.name}`)}</legend>
              {section.fields.map((field) => {
                const Renderer = getRenderer(field.config.component)
                return <Renderer key={field.name} {...form.getFieldProps(field.name)} />
              })}
            </fieldset>
          )
        }
        return section.fields.map((field) => {
          const Renderer = getRenderer(field.config.component)
          return <Renderer key={field.name} {...form.getFieldProps(field.name)} />
        })
      })}
    </form>
  )
}

ResolvedField

typescript
interface ResolvedField {
  name: string
  config: FieldConfig
  proxy: FieldProxy
}

ResolvedAction

typescript
interface ResolvedAction {
  name: string
  config: ActionConfig
  execute(): void | Promise<void>
}

FieldGroup

typescript
interface FieldGroup {
  name: string
  config: GroupConfig
  fields: ResolvedField[]
}

FormSection

typescript
type FormSection =
  | { kind: 'group'; name: string; config: GroupConfig; fields: ResolvedField[] }
  | { kind: 'ungrouped'; fields: ResolvedField[] }

Released under the MIT License.