FormRenderer
The main component that renders a FormSchema as an interactive multi-step form.
Import
import { FormRenderer } from "@schema-forms-data/renderer";
// or via the meta-package
import { FormRenderer } from "@schema-forms-data/react";
Props
Required
| Prop | Type | Description |
|---|---|---|
schema | FormSchema | The form schema. See Core Types. |
Events
| Prop | Type | Description |
|---|---|---|
onSubmitStep | (stepIndex: number, data: Record<string, unknown>) => Promise<void> | Called when the user advances to the next step. Use it to save drafts. |
onComplete | (data: Record<string, unknown>) => Promise<void> | Called on final submit with all accumulated values from all steps. |
onValuesChange | (values: Record<string, unknown>) => void | Called on every field change in the current step — useful for autosave. |
Visual
| Prop | Type | Default | Description |
|---|---|---|---|
formTitle | string | undefined | Title displayed at the top of the form. |
template | string | null | "moderno" | Visual template ID. See Templates. |
className | string | undefined | Extra CSS class on the root wrapper. |
StepIndicator | React.ComponentType<StepIndicatorProps> | built-in | Custom step indicator component. |
Data
| Prop | Type | Default | Description |
|---|---|---|---|
initialValues | Record<string, unknown> | {} | Initial field values when the form mounts. |
initialStep | number | 0 | Initial step (0-based index). |
externalData | Record<string, unknown> | {} | Data available for variable interpolation ({{evento.nome}}) and conditionals with source: 'evento'. |
fieldErrors | Record<string, string> | {} | Field errors from the server (e.g. a 422 response), applied after submit. |
Integrations
| Prop | Type | Description |
|---|---|---|
uploadFile | (file: File, fieldName: string, onProgress?: (pct: number) => void) => Promise<string> | Called when the user selects a file. Return the upload ID or URL. |
deleteUploadedFile | (uploadId: string) => Promise<void> | Called automatically before replacing a file, to delete the previous one from storage. Errors are silenced. |
cepLookup | (cep: string, signal?: AbortSignal) => Promise<CepLookupResult> | ZIP/postal code lookup. Defaults to the public ViaCEP API. |
resolveTermsUploadUrl | (uploadId: string) => Promise<string> | Resolves the preview URL of a terms PDF from an uploadId. |
fieldResolvers | FieldResolvers | Dynamic per-field props resolved at render time. See Injection. |
validatorMapper | ValidatorMapper | Custom (async) validation functions. See Validation. |
componentMapper | ComponentMapper | Replaces the default component for any field type. See Component Mapper. |
paymentMethodOptions | { porDia?: PaymentOption[]; todosOsDias?: PaymentOption[] } | Custom options for the payment_method field. |
Submit error handling
If onSubmitStep or onComplete throws, FormRenderer catches the error, hides the spinner, and displays the error message below the navigation buttons. The user can correct and retry without reloading the page.
Reset the form
To reset the form programmatically, use the useFormApi() hook from a child component:
import { useFormApi } from "@schema-forms-data/renderer";
function ResetButton() {
const api = useFormApi();
return <button onClick={() => api.reset()}>Start over</button>;
}
Full example
import { FormRenderer } from "@schema-forms-data/renderer";
import { schema } from "./mySchema";
export function RegistrationForm() {
return (
<FormRenderer
schema={schema}
formTitle="Event Registration"
template="acampamento"
externalData={{
"evento.nome": "Camp 2026",
"evento.valor": 5000,
"evento.id": "abc123",
}}
initialValues={{ nome: "John Doe" }}
uploadFile={async (file, fieldName, onProgress) => {
const form = new FormData();
form.append("file", file);
form.append("campo", fieldName);
const res = await fetch("/api/upload", { method: "POST", body: form });
const { id } = await res.json();
return id;
}}
deleteUploadedFile={async (uploadId) => {
await fetch(`/api/upload/${uploadId}`, { method: "DELETE" });
}}
cepLookup={async (cep) => {
const res = await fetch(`/api/cep/${cep}`);
return res.json(); // { logradouro, bairro, cidade, estado }
}}
validatorMapper={{
cpfUnico: async (value, _all, _cfg, external) => {
const exists = await api.checkCpf(
String(value),
String(external["evento.id"]),
);
return exists ? "CPF already registered for this event" : undefined;
},
}}
fieldErrors={serverErrors} // { email: 'Invalid email' }
onValuesChange={(values) =>
sessionStorage.setItem("draft", JSON.stringify(values))
}
onSubmitStep={async (stepIndex, data) => {
await api.saveDraft(stepIndex, data);
}}
onComplete={async (values) => {
await api.finalize(values);
}}
/>
);
}
Custom StepIndicator
Replace the built-in step indicator with your own component:
import type { StepIndicatorProps } from "@schema-forms-data/renderer";
function MyIndicator({ steps, currentStep, onStepClick }: StepIndicatorProps) {
return (
<div className="indicator">
{steps.map((step, i) => (
<button
key={step.id}
className={
i === currentStep ? "active" : i < currentStep ? "done" : ""
}
onClick={() => i < currentStep && onStepClick?.(i)}
>
{step.label}
</button>
))}
</div>
);
}
<FormRenderer
schema={schema}
StepIndicator={MyIndicator}
onComplete={handler}
/>;
interface StepIndicatorProps {
steps: Array<{ id: string; label?: string; icone?: string }>;
currentStep: number;
onStepClick?: (index: number) => void;
}