Internals: Form Engine Architecture
This document provides a deep-dive analysis of the FormEngineProvider and its core state management strategy.
1. Single Source of Truth
The engine is built around a single FormSchema object. Every aspect of the form—from UI structure to validation logic—is derived from this JSON.
The Role of FormEngineProvider
The provider initializes the react-hook-form context and bridges it with the DFBE's dynamic logic.
// Core initialization logic inside FormEngineProvider const methods = useForm({ mode: "all", defaultValues: propDefaultValues, resolver: (values) => { // Schema is re-generated on every validation cycle const dynamicSchema = generateZodSchema(allFields, values, datasetLookups); return zodResolver(dynamicSchema)(values); }, });
2. Step Navigation & Validation
Navigation in DFBE is "safe by design." You cannot move to the next step unless the current step's fields are valid.
handleNext Logic
- Extract Field Names: It flattens all fields in the current step into an array of paths (e.g.,
["profile.name", "profile.email"]). - Trigger Validation: It calls
formMethods.trigger(paths). - Navigation Guards: If the current step is a
CustomStepor has a registeredStepGuard, the engine awaits the guard's callback. If the guard returnsfalse, navigation is blocked. - State Update: Only if both field validation and guards pass, it increments
currentStepIndex. - UX: Automatically triggers
window.scrollTo({ top: 0 })to reset the user's view.
initialStep — Starting on a Specific Step
By default, FormEngineProvider starts at step index 0. To start on a specific step (e.g., resuming a partially-filled draft), pass the step's ID string via the initialStep prop.
<FormEngineProvider schema={schema} defaultValues={savedDraft} initialStep="education" // Jumps to the step with id: "education" > <MyFormShell /> </FormEngineProvider>
Important: Step IDs are assigned in the Studio Builder under Step Settings → Step ID. All step types (schema and custom) support editable IDs.
If the provided ID doesn't match any step in the schema, the engine falls back to step 0 and emits a console.warn.
Note:
initialStepdoes NOT validate skipped steps. The host app is responsible for ensuringdefaultValuescontains valid data for steps before the initial step.
onStepChange — Tracking Step Position
Use onStepChange to be notified whenever the active step changes (forward or backward). This is useful for persisting the user's last-visited step for future resume.
<FormEngineProvider schema={schema} defaultValues={savedDraft} initialStep={lastStep} onStepChange={(stepId, stepIndex) => { // Persist the active step for resume localStorage.setItem(`draft_${formId}_lastStep`, stepId); }} > <MyFormShell /> </FormEngineProvider>
The callback signature is (stepId: string, stepIndex: number) => void, providing both the semantic ID (for persistence/routing) and the numeric index (for progress indicators).
setStepGuard
Host applications can register a custom validation callback via setStepGuard. This is typically used in CustomStep components to perform async checks (e.g. checking payment status) before moving forward.
3. The Dataset Lookup Optimization
When a dataset prop is provided, the engine performs a pre-computation phase during mount/update:
- Logic: It iterates through the dataset configuration and builds a
Mapfor every field ID associated with that dataset. - Complexity: This transform array -> Map is O(N) where N is the total items in the dataset.
- Performance: Once built, every field lookup during validation or rendering is O(1).
4. Contextual Data Flow
The FormEngineContext provides 15 distinct values to consumers:
- Navigation State:
currentStep,currentStepIndex,isFirstStep,isLastStep. - Navigation Actions:
handleNext,handleBack,setStepGuard. - Logic State:
showSummary,setShowSummary,validatedData,onFormSubmit. - Form Bindings:
formMethods(Raw RHF access). - Schema & Data:
schema,dataset,datasetLookups. - Registries:
customSteps(StepComponentRegistry viaStepRegistryContext).
5. Lifecycle Guardrails
The engine includes several safety checks:
- Empty Schema: If
schema.stepsis empty, it renders a hard error alert instead of crashing. - Unmapped Components: If a field type is not in the
ComponentRegistry, it renders a placeholder warning with instructions for the developer.