Skip to main content

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

PropTypeDescription
fallbackReactNode | (error, reset) => ReactNodeUI to show on error
onError(error: Error, errorInfo: React.ErrorInfo) => voidCallback when error occurs
resetKeystring | numberChange 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 different from render errors. They're handled through the form state:

Displaying Field Errors

const TextInput = ({ field }) => (
<div>
<input
value={field.value || ""}
onChange={(e) => field.onChange(e.target.value)}
className={field.errors.length > 0 ? "input-error" : ""}
/>
{field.errors.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>
);
}

Best Practices

  1. Always use error boundaries - Prevent form errors from crashing your app

  2. Show meaningful messages - Help users understand what went wrong

  3. Log errors - Track errors for debugging and monitoring

  4. Handle all error types - Validation, submission, and render errors

  5. Provide recovery options - Let users retry or reset the form

  6. Validate specifications - Check specs before rendering in development