Skip to content

API — driven-form

Pure JavaScript — no React. Import these from driven-form.

calculateFieldDependencies(field: Partial<FieldData> | null | undefined): FieldDependencies

Compute which fields a given field depends on for each expression type. All arrays are deduplicated.

Uses normalised property names (post-schemaParser): show, required, disabled, calculateValue, minLength, maxLength

ParameterTypeDescription
fieldPartial<FieldData> | null | undefined
clearASTCache(): void

Clear the AST cache — useful in tests to ensure fresh parsing

clearMemoizedData(): void

Clear memoized parse results — useful in tests

computeFieldLifecycle(field: Partial<FieldData>, data: FormData, fieldDetailMap: FieldDetailMap): FieldLifecycleState
ParameterTypeDescription
fieldPartial<FieldData>
dataFormData
fieldDetailMapFieldDetailMap
computeIsDisabled(disabled: string | boolean | undefined, data: FormData, fieldDetailMap: FieldDetailMap): boolean

Evaluate a disabled condition.

ParameterTypeDescription
disabledstring | boolean | undefinedboolean (static) or expression string (conditional)
dataFormDataCurrent form data
fieldDetailMapFieldDetailMap
computeIsRequired(required: string | boolean | undefined, data: FormData, fieldDetailMap: FieldDetailMap): boolean

Evaluate a required condition.

ParameterTypeDescription
requiredstring | boolean | undefinedboolean (static) or expression string (conditional)
dataFormDataCurrent form data
fieldDetailMapFieldDetailMap
computeIsShow(show: string | undefined, data: FormData, fieldDetailMap: FieldDetailMap): boolean

Evaluate a show expression.

ParameterTypeDescription
showstring | undefinedExpression string, boolean, or undefined
dataFormDataCurrent form data
fieldDetailMapFieldDetailMapField configs (for date-type coercion in expressions)
computeStepStateValue(fieldValue: unknown, isRequired: boolean, isMasked: boolean, originalValue: unknown): unknown

Compute whether a field is “satisfied” for step progress tracking.

A field is satisfied when:

  • It has a value, OR
  • It’s not required

Special case: masked fields compare against their original value (showing ”***” should still count as filled).

ParameterTypeDescription
fieldValueunknownCurrent field value
isRequiredbooleanWhether the field is currently required
isMaskedbooleanWhether the value is displayed as masked (e.g. ***)
originalValueunknownThe original unmasked value (used when isMasked is true)
createValidatorRegistry(): ValidatorRegistry

Create a new validator registry pre-loaded with built-in validators.

Custom validators shadow built-ins when registered with the same name.

extractDependencyData(dependencies: string[], data: FormData): FormData

Slice form data to only the fields a given field depends on. Pass this slice as dependencyData to field components so re-renders are limited to actual dependency changes.

ParameterTypeDescription
dependenciesstring[]Array of field names (usually FieldDependencies.all)
dataFormDataCurrent full form data
getCalculatedValue(calculateValue: string | undefined, data: FormData, fieldDetailMap: FieldDetailMap): unknown

Evaluate a calculateValue expression. Returns undefined if no expression is configured.

ParameterTypeDescription
calculateValuestring | undefinedExpression string, e.g. “%price% * %quantity%”
dataFormDataCurrent form data
fieldDetailMapFieldDetailMap
getClearedValue(fieldValue: unknown): unknown

The empty value a hidden field is reset to. Arrays keep their shape ([]) so multi-select components that map over the value don’t receive a string; everything else clears to ”.

ParameterTypeDescription
fieldValueunknown
getExpressionFunction(name: string): ExpressionFunction | undefined

Resolve a callable for the evaluator: consumer registrations shadow built-ins.

ParameterTypeDescription
namestring
isContainerNode(node: FieldData): boolean

True if this node is a container (has children) rather than a leaf input

ParameterTypeDescription
nodeFieldData
isDebugMode(): boolean
isEmptyValue(value: unknown): boolean

The engine-wide definition of “no value”: ” | null | undefined | []. Used by the required check and by hide-clearing. Note that 0 and false ARE values — a filled number/boolean field is not empty.

ParameterTypeDescription
valueunknown
parseExpression(expression: string | null | undefined, data: FormData, fieldDetailMap: FieldDetailMap, options: ParseExpressionOptions): unknown

Evaluate a %fieldName%-style expression string using current form data.

Steps:

  1. Extract %fieldName% tokens
  2. Resolve each token to its value from data (date fields get timestamp conversion)
  3. Strip % delimiters, leaving a plain JS expression
  4. Evaluate via safeEvaluate with a variable map

Results are memoized per (expression, resolved-values) pair.

ParameterTypeDescription
expressionstring | null | undefinede.g. “%country% == ‘IN’ && %hasGST% == ‘Yes’”
dataFormDataCurrent form data keyed by field name
fieldDetailMapFieldDetailMapField configs (used to detect date fields for timestamp coercion)
optionsParseExpressionOptions{ returnIfVariableValueNull } — see ParseExpressionOptions
parseSchema(rawSchema: DrivenFormSchema | Record<string, unknown>): ParsedSchema

Parse and normalise the schema returned by your backend API.

ParameterTypeDescription
rawSchemaDrivenFormSchema | Record<string, unknown>{ fields: […] } — an arbitrary container/leaf node tree.
parseVariables(exp: string, wrapperCh: string): string[]

Extract all %fieldName% references from an expression string.

ParameterTypeDescription
expstringExpression string, e.g. “%country% == ‘IN’”
wrapperChstringDelimiter character (default: ’%’)
registerExpressionFunction(name: string, fn: ExpressionFunction): void

Register a function callable from schema expressions, e.g. registerExpressionFunction('lookup', (table, key) => ...) enables calculateValue: "lookup(%rates%, %country%)".

Re-registering a name replaces it (and invalidates memoized expression results). Registering a built-in name (sum/count/min/max/avg) shadows it. Throws on names that aren’t valid identifiers or are reserved by the evaluator (Number, String, Boolean, Math, Date, …).

ParameterTypeDescription
namestring
fnExpressionFunction
safeEvaluate(expression: string, variables: Record<string, unknown>): unknown

Safely evaluate an expression string with the given variable bindings.

ParameterTypeDescription
expressionstringe.g. “CityId == 1 && StateId > 0”
variablesRecord<string, unknown>e.g. { CityId: 1, StateId: 5 }
setDebugMode(enabled: boolean): void
ParameterTypeDescription
enabledboolean
shouldClearDataOnHide(isShow: unknown, fieldValue: unknown): boolean

Returns true if the field’s value should be cleared on hide. A field with a value that becomes hidden should be cleared so that hidden data doesn’t pollute the submitted form state. 0 and false count as values and are cleared too.

ParameterTypeDescription
isShowunknown
fieldValueunknown
unregisterExpressionFunction(name: string): void

Remove a consumer-registered expression function. A shadowed built-in becomes reachable again. No-op for names that were never registered.

ParameterTypeDescription
namestring

class

class

class

PropertyTypeDescription
dataRef?ReadonlyRef<FormData>Read-only live view of this scope’s data, for event-time reads. At the root this is the form’s dataRef; inside a row scope it is a getter view onto that row’s values object. Reading it does not subscribe — same contract as errorsRef.
errorsRefMutableRefObject<Record<string, string>>Ref containing current errors — writable so useValidation can sync it. Fields read this without subscribing to error state.
fieldDetailMapFieldDetailMapFlat map of all field configs (post-parse), keyed by field name. Stable after form load. Used by expressions that need field type info (e.g. Date fields need special handling in expression evaluation).
focusElem?FocusElemScroll (and optionally focus) a field by name
markFieldLoaded(fieldName: string) => voidMark a field as loaded — its lazy component resolved, or the field was removed. Called by the inner sentinel on Suspense resolve, and by the outer sentinel’s cleanup when the field unmounts entirely.
markFieldPending(fieldName: string) => voidMark a field as pending — its lazy component has not yet resolved.
registerBlockFieldShowState(fieldName: string, isShow: boolean) => voidRegister this field’s visibility. Called by useBlockField after computing isShow from the show expression.
registerBlockFieldStepState(fieldName: string, value: unknown) => voidRegister this field’s step state value. Step state is truthy when the field is satisfied (filled or not required).
registerBlockFieldVALID(fieldName: string, rules: unknown) => voidRegister this field’s validation rules. Called by useBlockField on mount and when isShow/isRequired changes. Internally de-dupes by JSON-stringifying rules — safe to call on every render.
registerFocusTarget?(name: string, element: Element | null) => voidRegister a DOM element for focus/scroll-to-error under an explicit name. Row scopes use this to register inputs under their scoped key (“directors.r3.name”) so focusElem can reach inside repeater rows. Pass null to deregister.
scopePath?stringThe scoped-key prefix for field names registered through this context (e.g. “directors.r3” inside row r3 of a root-level repeater). Undefined/” at the root. Used by nested scopes to build their own paths.
setErrorsDispatch<SetStateAction<Record<string, string>>>Directly set errors (used by fields that do their own async validation)
stepAstateRefRefObject<StepAState>Ref containing current step state. Fields read this without subscribing.
unregisterBlockFieldShowState(fieldName: string) => voidUnregister show state. Called on unmount.
unregisterBlockFieldStepState(fieldName: string) => voidUnregister step state. Called when field becomes hidden or unmounts.
unregisterBlockFieldVALID(fieldName: string) => voidUnregister this field’s validation rules. Called by useBlockField when field becomes hidden or unmounts.
updateData(fieldName: string, value: unknown) => voidUpdate a single field’s value in form data
validate(fieldName: string | string[], value: unknown, options?: object) => Promise<void>Validate a field (or list of fields) against current VALID rules
validatorRegistry?ValidatorRegistryValidator registry used by useBlockField to resolve field.validation rules. Passed through from useDynamicField — consumers can register custom validators.
VALIDRef?RefObject<ValidRules>Ref containing current VALID rules. Passed to validate() calls so they always use the latest rules without creating a dependency.
valueResolversRef?MutableRefObject<Record<string, (data: FormData) => unknown>>Registry of value resolvers for SCOPED field names (e.g. “directors.r3.name”). A row scope registers one resolver per row field alongside its VALID rules; submit validation uses these to read values that don’t exist as flat data keys. Flat fields never appear here.
PropertyTypeDescription
registerBlockFieldShowState(fieldName: string, isShow: boolean) => void
registerBlockFieldStepState(fieldName: string, value: unknown) => void
registerBlockFieldVALID(fieldName: string, rules: unknown) => void
stepAstateRefRefObject<StepAState>
unregisterBlockFieldShowState(fieldName: string) => void
unregisterBlockFieldStepState(fieldName: string) => void
unregisterBlockFieldVALID(fieldName: string) => void

Parse-time dependency graph over calculateValue fields. Built by parseSchema (see core/calcGraph.ts); consumed by updateData to settle calculated values synchronously in a single write.

PropertyTypeDescription
cyclicstring[]Calculated fields excluded from propagation because they sit on a cycle
dependentsRecord<string, string[]>Field name → calculated fields whose calculateValue references it
orderstring[]All calculated fields in topological evaluation order (cyclic fields excluded)
orderIndexRecord<string, number>Evaluation position per calculated field (for ordering affected subsets)

Props every registered CONTAINER component receives.

A container groups and lays out its children — it holds no value of its own. The engine renders the children (recursively) and hands them over; the container only decides how to wrap them: card, row, tabs, accordion, etc.

Container show expressions are evaluated by the engine BEFORE the component renders — a hidden container never mounts at all.

PropertyTypeDescription
childrenReactNodeThe container’s already-rendered children — place them in your layout
fieldFieldDataThe container node config (label, order, columnGrid, custom metadata…)
indexnumberSibling index of this container within its parent (0-based)

The subset of form state and actions exposed to children. Received via the children render prop:

PropertyTypeDescription
allFieldsLoadedbooleanTrue once all lazy field components have resolved their Suspense boundaries
dataFormData
errorsRecord<string, string>
fieldRegistryFieldRegistryThe registry passed to — pass through to a sibling
focusElemFocusElemProgrammatically focus/scroll to a field by name
getValues() => FormData
handleContinue() => Promise<HandleContinueResult>
isValidatingRecord<string, boolean>Per-field async validation in-flight status — true while a Promise is pending
refFuncRefFunc
schemaDrivenFormSchema
setError(fieldName: string, message: string | null) => void
setErrorsDispatch<SetStateAction<Record<string, string>>>
setTreeDispatch<SetStateAction<FieldData[]>>
setValue(fieldName: string, value: unknown, options?: object) => Promise<void>
treeFieldData[]
updateData(fieldName: string, value: unknown) => void
validate(fieldName: string | string[], value: unknown, options?: object) => Promise<void>

The root schema returned by your backend API.

driven-form expects one active form per schema object. Multi-step forms can be implemented by fetching a new schema per step.

PropertyTypeDescription
fieldsFieldSchema[]Root node tree. Containers (nodes with fields) nest arbitrarily; leaves are inputs.
id?string | numberForm identifier — returned to onFormLoad callback

Props every registered field component receives from RenderDynamicFields.

The field component is responsible for:

  • Calling useBlockField() internally to get isShow, isRequired, isDisabled
  • Rendering nothing (or returning null) when isShow is false
  • Calling onChange() with the new value when the user interacts
  • Wiring refFunc to the actual DOM input element (for focus management)
  • Wiring label, helpText, required, disabled to appropriate ARIA attributes
PropertyTypeDescription
columnGrid?numberGrid column span for layout (from field.columnGrid)
currentGridPosition?numberCurrent cumulative grid position (for reducePaddingLeft/Right logic)
dependencyDataFormDataPre-filtered slice of form data containing only the fields this field depends on (from its show/required/disabled/calculateValue expressions). Pass this to useBlockField as dependencyData.
fieldFieldDataNormalised field config from the backend schema
fieldError?string | nullCurrent validation error for this field, if any
fieldValueunknownCurrent value from form data
isFieldValidating?booleanTrue while an async validator for this field is still pending. Use this to show a spinner, disable submit, etc.
onBlur?() => voidCall when the input loses focus. driven-form uses this to run ‘blur’-mode validators (e.g. async uniqueness checks). Wire to your input’s onBlur event.
onChange(value: unknown) => voidCall with the new field value when the user makes a change. driven-form handles updateData + validate internally.
refFunc?(element: Element | null) => voidAttach to your DOM input element to enable focus-on-error behaviour. Pass the element (or null to deregister).

A field after schemaParser normalisation.

  • required reflects the validation array (a ‘required’ rule implies true)
  • Date values have time component stripped
  • Numeric values coerced to string for consistent validation

Note: Defined independently (not via Omit) because TypeScript loses explicit property types when Omit is applied to interfaces with string index signatures.

PropertyTypeDescription
calculateValue?string
columnFields?FieldData[]Recursive: column fields for table/repeater fields
columnGrid?number
disabled?string | booleanDisabled condition — boolean or expression string
fields?FieldData[]Child nodes. Present = this node is a CONTAINER (groups children, holds no value). Absent = this node is a LEAF (a real input).
helpText?string
isHidden?boolean
label?string
namestringUnique identifier — used as the key in form data
options?FieldOption[]
ordernumber
placeholder?string
required?string | booleanRequired condition — boolean or expression string
show?stringShow expression — always a string after parsing
typestringField type — must match a registered type in your FieldRegistry
validation?ValidationRule[]
value?unknown
PropertyTypeDescription
allstring[]Union of all dependency categories below
calculatedstring[]Fields referenced in calculateValue
disabledstring[]Fields referenced in the disabled expression
minMaxstring[]Fields referenced in minLength / maxLength expressions (dynamic length constraints)
requiredstring[]Fields referenced in the required expression
showstring[]Fields referenced in the show expression
PropertyTypeDescription
calculatedValueunknown
isDisabledboolean
isRequiredboolean
isShowboolean

A single option for dropdown / radio / multi-select fields

PropertyTypeDescription
idstring | numberStored as the field value when selected
labelstringDisplayed to the user

Interface for the field type registry

PropertyTypeDescription
containerTypes() => string[]Returns all registered container type strings
get(type: string) => FieldComponent | undefinedReturns the registered leaf component, or undefined if not found
getContainer(type: string) => ContainerComponent | undefinedReturns the registered container component, or undefined if not found
has(type: string) => booleanReturns true if a leaf component is registered for this type
hasContainer(type: string) => booleanReturns true if a container component is registered for this type
register(type: string, component: FieldComponent) => voidRegister a component for a LEAF field type.
registerContainer(type: string, component: ContainerComponent) => voidRegister a component for a CONTAINER type (a node with fields).
types() => string[]Returns all registered leaf type strings

A single field definition as received from the backend.

After parsing, this becomes FieldData (see internal.ts). Expression strings use %fieldName% syntax: “%country% == IN”

PropertyTypeDescription
calculateValue?stringCalculated value expression. Field value is auto-derived when dependencies change. Expression: “%price% * %quantity%”
columnFields?FieldSchema[]Column field definitions for composite fields (e.g. table rows). Each entry is a full FieldSchema — recursive composition.
columnGrid?numberGrid column span hint, e.g. 1–12 for a 12-column layout
disabled?string | booleanDisabled condition. Boolean or expression string. Expression: “%isVerified% == true”
fields?FieldSchema[]Child field definitions. A node with fields is a CONTAINER — it groups and lays out its children but holds no value of its own. A node without fields is a LEAF — a real input that stores a value in form data.
helpText?stringHelper text displayed below the field
isHidden?booleanWhen true, field value is kept in form data but the field is never rendered. Useful for fields needed by expressions but not shown to the user.
label?stringDisplay label
namestringUnique identifier — used as the key in form data
options?FieldOption[]Static options list
ordernumberRender order within its section (ascending)
placeholder?stringPlaceholder text for input fields
required?string | booleanRequired condition. Boolean or expression string. Expression: “%companyType% == Private”
show?stringShow condition. If omitted, field is always shown. Expression: “%country% == IN && %hasGST% == Yes”
typestringField type — must match a registered type in your FieldRegistry
validation?ValidationRule[]Ordered list of validation rules applied to this field
value?unknownInitial value

Params passed to onSave when the user submits the form

PropertyTypeDescription
dataFormDataAll current field values
fieldDetailMapFieldDetailMapAll field configs (for building the save payload)
schemaDrivenFormSchemaOriginal raw schema from the backend
PropertyTypeDescription
calcGraphCalcGraphDependency graph for synchronous calculated-value propagation
fieldDetailMapFieldDetailMapEvery LEAF node’s config, keyed by name (flat)
fieldValuesFormDataInitial values for every LEAF node, keyed by name (flat)
treeFieldData[]Root node tree — containers (nodes with fields) and leaves, sorted by order at every level.
PropertyTypeDescription
returnIfVariableValueNull?booleanIf true and any variable resolves to null, return null immediately instead of evaluating (useful for conditional-required short-circuits).

One row of a repeater, as exposed to the consumer’s component

PropertyTypeDescription
errorsRecord<string, string>This row’s validation errors, keyed by row-local field name
idstringStable row identity — use as the React key; survives remove/reorder
indexnumberCurrent display position (0-based). Changes on reorder; id does not.
valuesFormDataThis row’s values object — the same object stored in the form data array

The resolved result of looking up a validator for a field

PropertyTypeDescription
fnValidatorFn
message?string
validateOn?ValidationModeInherited from the schema rule — controls when this validator runs
PropertyTypeDescription
dependencyData?FormDataPre-filtered slice of form data — only the fields this field depends on (computed in RenderDynamicFields via fieldDependencyDataMap).
fieldFieldDataNormalised field config
fieldError?string | nullCurrent error for this field
fieldValueunknownCurrent value of this field
options?object
PropertyTypeDescription
fieldErrorstring | null | undefinedCurrent validation error — same as fieldError prop, exposed for convenience
isDisabledbooleanWhether this field is disabled right now (based on disabled expression)
isRequiredbooleanWhether this field is required right now (based on required expression)
isShowbooleanWhether this field should be rendered (based on show expression)
updateData(fieldName: string, value: unknown) => voidDirect access to updateData for fields that need custom update logic
validate(fieldName: string | string[], value: unknown) => voidDirect access to validate for fields that do their own validation timing

Input props for useDynamicField

PropertyTypeDescription
onFormLoad?(formId: string | number | undefined) => voidCalled after loadFormData processes the schema. Receives the form id from the schema — useful for multi-step forms.
onSave(params: OnSaveParams) => voidCalled when the user submits the form after all validation passes. This is where you make your API call to save the data.
validatorRegistry?ValidatorRegistryOptional custom validator registry. If not provided, a default registry with built-in validators is used. Use this to register domain-specific validators (e.g. PAN, GST number).

Everything useDynamicField returns

PropertyTypeDescription
allFieldsLoadedbooleanTrue once all field components wrapped in FieldComponentSuspense have resolved. handleContinue is blocked while this is false — lazy fields that haven’t loaded yet haven’t registered their validation rules, so required checks would be skipped. Wire this to your submit button’s disabled state alongside isValidating.
blockFieldFnsBlockFieldFnsFunctions for useBlockField to register/unregister its state. Pass to RenderDynamicFields as the blockFieldFns prop.
contextValueBlockFieldContextValuePre-built context value — pass this to BlockFieldProvider so that useBlockField inside field components can access form state.
dataFormDataAll current field values keyed by field name
errorsRecord<string, string>Current validation errors keyed by field name
fieldDetailMapFieldDetailMapFlat field config map keyed by field name
focusElemFocusElemProgrammatically focus/scroll to a field by name
getValues() => FormDataRead the current form data synchronously, without subscribing to it. Prefer this over the data state inside callbacks/effects that don’t want to re-run on every keystroke (data is fine in render).
handleContinue() => Promise<HandleContinueResult>The submit handler. Wire this to your form’s submit button.
isValidatingRecord<string, boolean>Per-field async validation in-flight status. True for a field name while its async validator Promise is pending. Use this to render a spinner or disable submit.
loadFormData(schema: DrivenFormSchema) => voidLoad the schema returned by your API. Call this after fetching the schema from your backend.
onSubmitValidation(options?: object) => Promise<boolean | object>Run full-form validation — returns whether the form is valid
refFuncRefFuncAttach to a DOM element to enable focus-on-error. Pass to RenderDynamicFields as the refFunc prop.
schemaDrivenFormSchemaRaw schema as received from the backend
setError(fieldName: string, message: string | null) => voidSet or clear a single field’s error directly — e.g. from a server-side validation response on submit. Pass null to clear.
setErrorsDispatch<SetStateAction<Record<string, string>>>Directly set errors (e.g. from server-side validation response)
setTreeDispatch<SetStateAction<FieldData[]>>Directly update the node tree (e.g. for multi-step back navigation)
setValue(fieldName: string, value: unknown, options?: object) => Promise<void>Set a field’s value and (by default) validate it — the same two steps a field’s own onChange performs. Pass { validate: false } to only update the value. Resolves once validation settles (or immediately if skipped) — safe to leave unawaited for fire-and-forget use.
showFieldShowFieldStateShow/hide state keyed by field name (populated by useBlockField as fields mount)
treeFieldData[]Parsed, ordered node tree. Containers (nodes with fields) nest arbitrarily; leaves are inputs.
updateData(fieldName: string, value: unknown) => voidManually update a single field value. Normally called via the onChange prop passed to field components.
validate(fieldName: string | string[], value: unknown, options?: object) => Promise<void>Validate a single field (or array of fields)
VALIDRefMutableRefObject<ValidRules>Stable ref to VALID rules — passed through to validate calls
PropertyTypeDescription
dependencyData?FormDataDependency slice from the parent renderer. Includes any form-level fields the row template references (hoisted automatically), which the rows resolve via fallback.
fieldFieldDataThe repeater field config (must have columnFields as the row template)
fieldError?string | nullError for the repeater itself (repeater-level validators)
fieldValueunknownCurrent field value — the rows array (or undefined before seeding)
PropertyTypeDescription
addRow(initialValues?: FormData) => voidAppend a row (optionally pre-filled). No-ops at maxRows.
canAddRowbooleanFalse when maxRows is reached — wire to the add button’s disabled
canRemoveRowbooleanFalse when minRows is reached — wire to remove buttons’ disabled
externalDepsFormDataForm-level values the rows fall back to — RepeaterRow merges these under row values
fieldErrorstring | nullRepeater-level error (e.g. a whole-array validator’s message)
getRowScope(rowId: string) => BlockFieldContextValueScoped context for a row — stable per row id for the row’s lifetime
isDisabledboolean
isRequiredboolean
isShowboolean
moveRow(rowId: string, toIndex: number) => voidMove a row to a new index. Errors and identity travel with the row.
removeRow(rowId: string) => voidRemove a row by id (also clears its errors). No-ops at minRows.
rowsRepeaterRowData[]Ordered rows — render each through RepeaterRow with key={row.id}
rowTemplateFieldData[]Row template (sorted columnFields) — RepeaterRow renders this

A single validation rule attached to a field.

Built-in names: “required” | “minLength” | “maxLength” | “min” | “max” | “email” | “url” | “pattern” Custom names: any string you register via the validator registry

PropertyTypeDescription
message?stringError message shown when this rule fails
namestringRule identifier — matches a built-in or a registered custom validator
validateOn?ValidationModeWhen this rule is evaluated. Defaults to ‘change’. Set to ‘blur’ for async validators to avoid firing on every keystroke. Set to ‘submit’ for expensive checks that only need to run on form submission.
value?unknownArgument for parameterised validators (e.g. minLength: 3)

Interface for the validator registry

PropertyTypeDescription
has(name: string) => booleanReturns true if a validator is registered for this name
register(name: string, validator: ValidatorEntry) => voidRegister a custom validator by name. If the validator needs a parameter (e.g. minLength), pass a factory.
resolve(rule: object) => ResolvedValidator | nullResolve a validation rule to a { fn, message, validateOn } triple. Returns null if no validator found for the name.
type ContainerComponent = ComponentType<ContainerComponentProps>

A React component that can be registered as a container type

type DrivenFormErrorCode = 'EXPRESSION_PARSE_ERROR' | 'EXPRESSION_EVAL_ERROR' | 'SCHEMA_VALIDATION_ERROR' | 'DUPLICATE_FIELD_NAME' | 'CIRCULAR_DEPENDENCY' | 'UNREGISTERED_FIELD_TYPE' | 'UNREGISTERED_VALIDATOR'

errors.ts — Typed error classes

All driven-form errors have a code property for programmatic handling.

type ExpressionFunction = (...args: unknown[]) => unknown

A function callable from schema expressions. Must be pure and synchronous.

type FieldComponent = ComponentType<FieldComponentProps>

A React component that can be registered as a field type

type FieldDetailMap = Record<string, FieldData>

All parsed field configs, keyed by field name

type FocusElem = (fieldName: string, options?: object) => void

Scroll to (and optionally focus) a field by name. Used by validation to jump to the first error field on submit.

type FormData = Record<string, unknown>

All current field values, keyed by field name

type HandleContinueResult = object | object | object

Discriminated result of handleContinue — says what happened so consumers can react (show a toast, log, retry) instead of guessing from silence.

  • ‘saved’ — validation passed and onSave was called
  • ‘blocked’ — submit refused; reason: 'fields-loading' means lazy field components haven’t resolved yet, so their validation rules aren’t registered (watch allFieldsLoaded to disable the button instead)
  • ‘validation-failed’ — one or more fields failed; errors is keyed by field name
type ParseExpressionFn = (expression: string | null | undefined, data: FormData, fieldDetailMap?: FieldDetailMap, options?: ParseExpressionOptions) => unknown
type RefFunc = (element: Element | null) => void

Attach a DOM element to the multi-ref manager. Pass null to deregister (standard ref callback pattern).

type RequireKeys = T & Required<Pick<T, K>>

Make specific keys of T required

type ShowFieldState = Record<string, boolean>

Show/hide state per field name (false = hidden, true or undefined = shown)

type StepAState = Record<string, unknown>

Step/fill state per field name. Truthy = field is satisfied (filled or not required). Used by onSubmitValidation to determine overall form validity.

type UnregisteredFieldBehaviour = 'warn' | 'throw' | 'silent' | 'custom'

What to do when RenderDynamicFields encounters an unregistered field type

type ValidationMode = 'change' | 'blur' | 'submit'

When a validation rule is evaluated.

  • ‘change’ (default) — on every value change. Use for cheap sync checks (required, format).
  • ‘blur’ — when the user leaves the field. Use for async checks (server uniqueness).
  • ‘submit’ — only when the form is submitted. Use for expensive or final server checks.

On submit, ALL rules run regardless of their validateOn setting. On blur, ‘change’ and ‘blur’ rules run. On change, only ‘change’ rules run.

type ValidatorEntry = ValidatorFn | ValidatorFactory

Either a direct validator or a factory (for parameterised rules)

type ValidatorFactory = (ruleValue: unknown) => ValidatorFn

A parameterised validator factory. Called with the rule’s value from the schema, returns a ValidatorFn.

Example: minLength factory is called with 3, returns a validator that checks value.length >= 3.

type ValidatorFn = (value: unknown, formData?: FormData, field?: FieldData) => string | null | undefined | Promise<string | null | undefined>

A validator function. Returns an error message string if invalid, null/undefined if valid. Can return a Promise for async validation (e.g. server-side uniqueness checks).

type ValidRules = Record<string, unknown>

Validation rules per field name. Populated by useBlockField as fields mount, cleared as they unmount.

type Values = T[keyof T]

Extract value types from a const object

const