Error Handling
Handle errors gracefully in your Forma-powered forms.
FormaErrorBoundary
Wrap forms with FormaErrorBoundary to catch render errors:
import { FormRenderer, FormaErrorBoundary } from "@fogpipe/forma-react";
function MyForm() {
return (
<FormaErrorBoundary
fallback={<div>Something went wrong with the form</div>}
onError={(error) => console.error("Form error:", error)}
>
<FormRenderer
spec={mySpec}
components={components}
onSubmit={handleSubmit}
/>
</FormaErrorBoundary>
);
}
Props
| Prop | Type | Description |
|---|---|---|
fallback | ReactNode | (error, reset) => ReactNode | UI to show on error |
onError | (error: Error, errorInfo: React.ErrorInfo) => void | Callback when error occurs |
resetKey | string | number | Change to reset error state |
Fallback Options
Static Fallback
Simple error message:
<FormaErrorBoundary fallback={<div>Form failed to load</div>}>
{/* ... */}
</FormaErrorBoundary>
Function Fallback
Access error details and reset function:
<FormaErrorBoundary
fallback={(error, reset) => (
<div className="error-container">
<h3>Form Error</h3>
<p>{error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
)}
>
{/* ... */}
</FormaErrorBoundary>
Error Logging
Log errors for debugging or monitoring:
<FormaErrorBoundary
onError={(error) => {
// Log to console
console.error("Form rendering error:", error);
// Send to error tracking service
errorTracker.captureException(error, {
context: "forma-form",
formId: spec.meta.id,
});
}}
fallback={<ErrorMessage />}
>
{/* ... */}
</FormaErrorBoundary>
Reset on Spec Change
Reset error state when the form specification changes:
function DynamicForm({ spec }) {
return (
<FormaErrorBoundary
resetKey={spec.meta.id} // Reset when form changes
fallback={<ErrorMessage />}
>
<FormRenderer spec={spec} components={components} />
</FormaErrorBoundary>
);
}
Validation Errors
Validation errors are not thrown as exceptions. They are part of the form state and must be read from field.errors or forma.errors. Do not wrap validation handling in try/catch blocks.
Validation errors are different from render errors. They're handled through the form state.
When to Show Errors
Forma runs validation immediately so that isValid and canProceed are always accurate. This means field.errors is populated before the user interacts with a field.
To avoid showing errors on untouched fields, use field.visibleErrors — it is pre-filtered by interaction state (touched or submitted):
const TextInput = ({ field }: TextComponentProps) => (
<div>
<input
value={field.value || ""}
onChange={(e) => field.onChange(e.target.value)}
onBlur={field.onBlur}
className={field.visibleErrors.length > 0 ? "input-error" : ""}
/>
{field.visibleErrors.map((error, i) => (
<p key={i} className="error-message" role="alert">
{error.message}
</p>
))}
</div>
);
field.errors — all validation errors, always populated (use for programmatic checks).
field.visibleErrors — errors filtered by touched/submitted state (use for UI display).
Alternatively, you can gate error display manually with field.touched:
{
field.touched && field.errors.length > 0 && (
<span className="error">{field.errors[0].message}</span>
);
}
Displaying Field Errors
const TextInput = ({ field }) => (
<div>
<input
value={field.value || ""}
onChange={(e) => field.onChange(e.target.value)}
onBlur={field.onBlur}
className={field.visibleErrors.length > 0 ? "input-error" : ""}
/>
{field.visibleErrors.map((error, i) => (
<p key={i} className="error-message" role="alert">
{error.message}
</p>
))}
</div>
);
Accessing All Errors
function FormErrors({ forma }) {
if (forma.errors.length === 0) return null;
return (
<div className="form-errors" role="alert">
<h4>Please fix the following errors:</h4>
<ul>
{forma.errors.map((error, i) => (
<li key={i}>
<strong>{error.field}:</strong> {error.message}
</li>
))}
</ul>
</div>
);
}
Error Severity
Handle errors and warnings differently:
function FieldFeedback({ errors }) {
const fieldErrors = errors.filter((e) => e.severity === "error");
const warnings = errors.filter((e) => e.severity === "warning");
return (
<>
{fieldErrors.map((error, i) => (
<p key={`error-${i}`} className="error">
{error.message}
</p>
))}
{warnings.map((warning, i) => (
<p key={`warning-${i}`} className="warning">
{warning.message}
</p>
))}
</>
);
}
Submission Errors
Handle errors during form submission:
function SubmitHandler({ forma }) {
const [submitError, setSubmitError] = useState<string | null>(null);
const handleSubmit = async () => {
setSubmitError(null);
try {
await forma.submitForm();
} catch (error) {
setSubmitError(
error instanceof Error ? error.message : "An unexpected error occurred",
);
}
};
return (
<div>
{submitError && (
<div className="submit-error" role="alert">
{submitError}
</div>
)}
<button onClick={handleSubmit} disabled={forma.isSubmitting}>
{forma.isSubmitting ? "Submitting..." : "Submit"}
</button>
</div>
);
}
Invalid Specification Handling
Handle cases where the Forma specification might be invalid:
import { validateFormaComprehensive } from "@fogpipe/forma-core";
function SafeFormRenderer({ spec, ...props }) {
const validation = useMemo(() => validateFormaComprehensive(spec), [spec]);
if (!validation.valid) {
return (
<div className="spec-error">
<h3>Invalid Form Specification</h3>
<ul>
{validation.errors.map((error, i) => (
<li key={i}>{error.message}</li>
))}
</ul>
</div>
);
}
return (
<FormaErrorBoundary fallback={<div>Form error</div>}>
<FormRenderer spec={spec} {...props} />
</FormaErrorBoundary>
);
}