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:
| Method | Description |
|---|---|
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): unknownfunction 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[] | undefinedfunction 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): SectionProgresstype 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
- Theming — customise colours, typography, and spacing
- Custom field types — build your own field components
- FormEngineRenderer — the all-in-one component