Docs
v1.4.1
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. Selects the rendering strategy based on schema.settings.displayMode:
    • stepped (default) — one section at a time with Back/Next/Submit buttons and a progress bar
    • classic — all visible sections rendered at once with a Submit button at the bottom
    • conversational — one question at a time with Enter key support and question-level progress
  4. Handles navigation, validation, and submission
  5. Manages draft persistence if settings.allowDraftSave is true

See Display modes for details on each mode.

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