Handling errors in React
Handle Qubeforms API errors in React forms with validation feedback and preserved input. Manage rate limits, network failures, duplicate submissions, and retries.
1. Separate local validation from API errors
Start with A React form component. Browser required and type="email" validation run before its submit handler for normal form submission. Add your own local checks for file count and size if needed, but keep the server response authoritative. Track pending and feedback state separately so a failed request leaves the visitor’s fields available.
2. Parse responses defensively
Keep existing field values after an error and clear them only after response.ok and result.ok === true. Use error.message for server feedback and render it as text, not HTML. The API does not promise a per-field error map, so do not assume error.details always contains field names. A proxy may return HTML or an empty body; guard JSON parsing and retain a fallback message. A network exception does not prove that the submission failed to arrive.
const result = await response.json().catch(() => null);
if (!response.ok || result?.ok !== true) {
setFeedback(result?.error?.message || `Could not confirm receipt (HTTP ${response.status}).`);
return;
}
// Reset fields only in the confirmed-success branch.3. Handle common status codes
Use the HTTP status and message together. Do not relabel every failure as a CORS problem or every 422 as an email-field error. A redirect-validation error can occur after storage, so inspect the inbox before repeating a submission with a corrected redirect.
402 The plan does not allow this feature, such as file uploads.
403 Origin or access restriction.
404 Form or requested resource unavailable.
413 Request or payload too large.
422 Validation, CAPTCHA, attachment, or redirect error; inspect error.message.
429 Rate, storage, or usage limit; follow Retry-After when present.
500 Server error; receipt may be uncertain.
503 Service or storage unavailable.4. Respect throttling
The standard submission limit is 20 attempts per form/IP per 10 minutes; advanced-plan controls can change it. Other limits can also return 429. Read Retry-After when present; Qubeforms returns a delay in seconds. Show that delay and prevent an immediate retry in your UI if you implement a cooldown. The basic React component displays it but does not implement a timer. Do not start a tight automatic retry loop.
5. Avoid duplicate submissions
The component’s ref and disabled fieldset prevent double clicks during a request. For deliberate retry support, send an Idempotency-Key generated once for the same logical submission and reuse it only when retrying the unchanged payload. Qubeforms returns the existing record for a reused key; changed payloads are not compared and may therefore be ignored. Clear the key for a new submission or after edits. Every attempt still consumes rate-limit capacity. Do not generate a new key on each automatic retry or submit on every render.
6. Handle uncertain outcomes and unmounts
If a timeout, cancellation, or network loss occurs, the server may already have stored the submission. Do not tell the visitor that cancelling removed it. Preserve the entered data while offering a clear next step and check for receipt before repeating the request. If you add AbortController on unmount, treat abort as an uncertain outcome, not an undo operation. Keep server messages in {feedback} or another text node rather than dangerouslySetInnerHTML.
7. Test the failure states
Test offline mode, a rejected origin, invalid CAPTCHA, an oversized file, a non-JSON error response, and rate limiting. Confirm that the button unlocks, entered values remain, and a successful response resets only once. A success can still be classified as Spam; do not infer delivery from the HTTP status alone.
Public form keys can appear in browser code. Read keys, management credentials, and webhook signing secrets must stay on the server.