A form that clears its fields on network failure destroys user work and guarantees the user abandons the operation — or at minimum re-enters all data under frustration. ISO 25010 reliability.recoverability requires the application to preserve state across failure events. For signup, checkout, and contact forms this is a direct conversion killer: a single network timeout during form submission can permanently lose a customer. The fix is architectural, not cosmetic: form state must live in React state or a form library, never be cleared in the catch block.
Low severity at the technical level, but high business impact — form abandonment after submission failure is a direct revenue loss vector for signup and checkout flows.
Never clear form state in error handlers. Keep field values in state, show an inline error, and expose a retry action that resubmits the preserved data.
// components/contact-form.tsx
const [fields, setFields] = useState({ email: '', message: '' })
const [status, setStatus] = useState<'idle' | 'submitting' | 'error' | 'success'>('idle')
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setStatus('submitting')
try {
await submitContact(fields)
setStatus('success')
} catch {
setStatus('error') // fields unchanged — user can retry
}
}
// In the render:
{status === 'error' && (
<p>
Submission failed. <button onClick={handleSubmit}>Try again</button>
</p>
)}
For long forms, consider persisting draft state to localStorage so data survives a hard refresh.
ID: error-resilience.graceful-degradation-shutdown.form-submission-retry
Severity: low
What to look for: Count all form submission handlers. Enumerate which handle submission failures with retry options vs. which silently fail or lose user input. Look at form submission handlers. Check whether they preserve user input on network failure and offer a retry option without re-entering data.
Pass criteria: Form submission preserves user input on network failure; user can retry or edit input before resubmitting. At least 80% of form submissions must preserve user input on failure and offer a retry mechanism.
Fail criteria: Form submission on network failure loses user input or offers no retry mechanism.
Skip (N/A) when: The application has no forms.
Cross-reference: For retry logic with backoff, see retry-logic-backoff.
Detail on fail: "Network failure during form submission clears all user input; user must re-enter data" or "No retry option after form submission failure"
Remediation: Preserve form state and offer retry:
// components/contact-form.tsx — form with retry
const [error, setError] = useState(false)
const onSubmit = async (data: FormData) => { try { await submitForm(data) } catch { setError(true) } }
{error && <p>Submission failed. <button onClick={() => onSubmit(formData)}>Try Again</button></p>}
export function SubmitForm() {
const [formData, setFormData] = useState({ email: '', message: '' })
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState(null)
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setSubmitting(true)
setError(null)
try {
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(formData),
})
if (!response.ok) throw new Error('Submission failed')
setFormData({ email: '', message: '' })
} catch (err) {
setError(err)
// User data is preserved in formData state
} finally {
setSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="Email"
/>
<textarea
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
placeholder="Message"
/>
{error && (
<div>
<p>Error: {error.message}</p>
<button type="submit" disabled={submitting}>
{submitting ? 'Retrying...' : 'Retry'}
</button>
</div>
)}
{!error && (
<button type="submit" disabled={submitting}>
{submitting ? 'Submitting...' : 'Submit'}
</button>
)}
</form>
)
}