Docs
v1.3.14
GitHubSite
React / <FormRenderer />

FormRenderer

The main component. It creates the engine, renders fields, handles navigation, validation, drafts and submission.

Basic usage

import { FormRenderer } from '@squaredr/fieldcraft-react'
import schema from './contact-form.json'

export default function ContactPage() {
  return (
    <FormRenderer
      schema={schema}
      onSubmit={async (response) => {
        await fetch('/api/submit', {
          method: 'POST',
          body: JSON.stringify(response),
        })
      }}
    />
  )
}

Props

Required

PropTypeDescription
schemaFormEngineSchemaThe form schema. Validated at mount time — invalid schemas throw FormEngineSchemaError.

Submission

PropTypeDescription
onSubmit(response: FormResponse) => void | Promise<void>Called when the form is submitted successfully. Receives the full FormResponse with values, metadata, scores.
adaptersSubmitAdapter | SubmitAdapter[]One or more submission adapters (HTTP, Supabase, Postgres, webhook). Run in parallel on submit.

Theming

PropTypeDescription
themeFormEngineThemeTheme object or preset. Controls colours, typography, spacing, and shape.
classNamestringCSS class added to the root form element.

Field registry

PropTypeDescription
componentsFieldRegistryCustom field component map. Merged with the default registry — your components override built-in ones for matching types.

Data

PropTypeDescription
prefillRecord<string, unknown>Initial values to prefill into the form. Keys are field IDs.
initialValuesRecord<string, unknown>Same as prefill — alternative prop name.
sessionTokenstringCustom session token. If not provided, a UUID is generated. Used to scope drafts.

Validators

PropTypeDescription
validatorsRecord<string, CustomValidator>Custom sync validators keyed by name. Referenced in schema via { type: 'custom', name: '...' }.
asyncValidatorsRecord<string, AsyncValidator>Custom async validators keyed by name. Referenced in schema via { type: 'async', endpoint: '...' }.

Labels

PropTypeDescription
prevLabelstringLabel for the "Back" button. Default: "Back".
nextLabelstringLabel for the "Next" button. Default: "Next".
submitLabelstringLabel for the "Submit" button. Default: "Submit".

Callbacks

PropTypeDescription
onSectionChange(sectionId: string, index: number) => voidCalled when the active section changes.
onFieldChange(fieldId: string, value: unknown) => voidCalled when any field value changes.
onReady() => voidCalled after the engine is initialised and the form is ready.
onValidationError(errors: Record<string, string[]>) => voidCalled when validation fails (on section change or submit).
onStateChange(state: FormState) => voidCalled on every state change. Use sparingly — this fires frequently.

Full example

import {
  FormRenderer,
  cleanPreset,
  defaultRegistry,
} from '@squaredr/fieldcraft-react'
import { createSupabaseAdapter } from '@squaredr/fieldcraft-adapters'
import { PainScaleField } from './custom-fields/PainScaleField'
import { validators } from '@/lib/validators'
import schema from './patient-intake.json'
import { supabase } from '@/lib/supabase'

const adapter = createSupabaseAdapter({
  client: supabase,
  table: 'intake_submissions',
})

export default function IntakePage() {
  return (
    <FormRenderer
      schema={schema}
      theme={cleanPreset}
      adapters={adapter}
      components={{ ...defaultRegistry, pain_scale: PainScaleField }}
      validators={validators}
      prefill={{ referral_source: 'website' }}
      onSubmit={async (response) => {
        console.log('Submitted:', response.schemaId, response.values)
      }}
      onSectionChange={(id, idx) => {
        console.log(`Section ${idx + 1}: ${id}`)
      }}
      submitLabel="Submit Intake Form"
    />
  )
}

How it works internally

FormRenderer is a wrapper that:

  1. Creates a FormEngine instance via useFormEngine(schema, options)
  2. Subscribes to state changes via useSyncExternalStore
  3. Renders the current section's visible fields using the field registry
  4. Handles navigation buttons (Next/Back/Submit)
  5. Shows the progress indicator based on settings.progressStyle
  6. Manages draft persistence if settings.allowDraftSave is true

The engine lives in a useRef and is created once. React Strict Mode double-mounts don't create multiple engines.

Using the engine directly

If FormRenderer doesn't fit your layout, use useFormEngine to get the engine and build your own UI:

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

function CustomForm({ schema }) {
  const engine = useFormEngine(schema, {
    onSubmit: async (response) => { /* ... */ },
  })

  return (
    <div>
      <h1>{engine.getSchema().title}</h1>
      <p>Progress: {engine.state.progressPercent}%</p>
      {/* Render fields manually */}
    </div>
  )
}

See Hooks for the full hook API.

Next steps

  • Hooks — useFormEngine, useFieldValue, useFieldError, useSectionProgress
  • Theming — customise the visual appearance
  • Custom field types — register your own field components
On this page