Docs
v1.3.14
GitHubSite
React / Hooks

Hooks

Four React hooks for accessing engine state, field values, validation errors, and navigation progress.

useFormEngine

Creates and manages a FormEngine instance. This is the primary hook — it returns the engine with all methods plus a reactive state property.

function useFormEngine(
  schema: FormEngineSchema,
  options?: EngineOptions
): FormEngine & { state: FormState }

Usage

import { useFormEngine } from '@squaredr/fieldcraft-react'

function CustomForm({ schema }) {
  const engine = useFormEngine(schema, {
    onSubmit: async (response) => {
      await fetch('/api/submit', { method: 'POST', body: JSON.stringify(response) })
    },
  })

  const { state } = engine

  return (
    <div>
      <h1>{engine.getSchema().title}</h1>
      <p>{state.progressPercent}% complete</p>

      {engine.getVisibleFields(state.currentSectionId).map((field) => (
        <div key={field.id}>
          <label>{field.label}</label>
          <input
            value={(state.values[field.id] as string) || ''}
            onChange={(e) => engine.setValue(field.id, e.target.value)}
            onBlur={() => engine.touchField(field.id)}
          />
          {state.errors[field.id]?.map((err) => (
            <p key={err} style={{ color: 'red' }}>{err}</p>
          ))}
        </div>
      ))}

      {state.canGoPrev && <button onClick={() => engine.prevSection()}>Back</button>}
      {state.canGoNext && <button onClick={() => engine.nextSection()}>Next</button>}
      {!state.canGoNext && <button onClick={() => engine.submit()}>Submit</button>}
    </div>
  )
}

EngineOptions

type EngineOptions = {
  mode?: 'controlled' | 'uncontrolled'
  initialValues?: Record<string, unknown>
  prefillValues?: Record<string, unknown>
  adapters?: SubmitAdapter | SubmitAdapter[]
  draftAdapter?: DraftAdapter
  analytics?: AnalyticsAdapter
  onSubmit?: (response: FormResponse) => void | Promise<void>
  onStateChange?: (state: FormState) => void
  onSectionChange?: (sectionId: string, index: number) => void
  onFieldChange?: (fieldId: string, value: unknown) => void
  validators?: Record<string, CustomValidator>
  asyncValidators?: Record<string, AsyncValidator>
  sessionToken?: string
}

Engine methods

The returned engine includes all FormEngine methods:

MethodDescription
setValue(id, value)Set a field's value
setValues(values)Set multiple values at once
touchField(id)Mark a field as touched (enables error display)
clearField(id)Clear a field's value
nextSection()Navigate to next section (validates current section first)
prevSection()Navigate to previous section
jumpTo(sectionId)Jump to a specific section
validate()Validate all visible fields
validateSection(sectionId)Validate a single section
submit()Submit the form
saveDraft()Save current state as a draft
loadDraft()Load a saved draft
clearDraft()Clear the saved draft
getVisibleSections()Get array of currently visible sections
getVisibleFields(sectionId)Get visible fields for a section
isFieldRequired(id)Check if a field is currently required
isFieldVisible(id)Check if a field is currently visible
isFieldDisabled(id)Check if a field is currently disabled
getFieldError(id)Get error messages for a field
getSchema()Get the original schema
subscribe(listener)Subscribe to state changes (returns unsubscribe function)
getState()Get current state snapshot
destroy()Clean up the engine

Stability

The engine is created once in a useRef and survives React Strict Mode double-mounts. The subscribe and getSnapshot functions passed to useSyncExternalStore are stable references.

useFieldValue

Subscribe to a single field's value. Re-renders only when that specific field changes.

function useFieldValue(engine: FormEngine, fieldId: string): unknown
function PriceDisplay({ engine }) {
  const price = useFieldValue(engine, 'total_price')

  return <p>Total: ${typeof price === 'number' ? price.toFixed(2) : ''}</p>
}

useFieldError

Subscribe to a field's validation errors. Returns the error array or undefined.

function useFieldError(engine: FormEngine, fieldId: string): string[] | undefined
function FieldWithError({ engine, fieldId }) {
  const errors = useFieldError(engine, fieldId)

  return (
    <div>
      {errors?.map((err) => (
        <p key={err} className="error">{err}</p>
      ))}
    </div>
  )
}

useSectionProgress

Subscribe to multi-step navigation state. Returns a progress object.

function useSectionProgress(engine: FormEngine): SectionProgress
type SectionProgress = {
  currentSectionId: string
  currentSectionIndex: number
  totalVisibleSections: number
  progressPercent: number
  visitedSectionIds: string[]
  canGoNext: boolean
  canGoPrev: boolean
}
function StepIndicator({ engine }) {
  const progress = useSectionProgress(engine)

  return (
    <div>
      Step {progress.currentSectionIndex + 1} of {progress.totalVisibleSections}
      <progress value={progress.progressPercent} max={100} />
    </div>
  )
}

Next steps

On this page