A user who fills out a multi-field signup form, hits a server error, and finds all their input cleared will abandon rather than re-type. ISO 25010 reliability.fault-tolerance requires that fault recovery not introduce secondary failures — clearing user data on error is exactly that. This is especially damaging in multi-step onboarding flows, payment forms, and file upload workflows where re-entering data has high friction. Catch blocks that call form.reset() or setState({}) on error are the most common defect pattern: the form correctly shows an error message but simultaneously destroys the data the user needs to re-submit.
High because error handlers that clear form state force users to re-enter all input after a server failure, causing direct abandonment in high-value conversion flows.
Ensure catch blocks never reset form state — only update the error message and re-enable the submit button.
// Wrong — destroys user input on error
} catch (error) {
form.reset()
setError('Submission failed')
}
// Right — preserves user input, only surfaces the error
} catch (error) {
setSubmitError('Submission failed. Your data is preserved — please try again.')
setIsSubmitting(false)
}
For multi-step flows, store step state in a parent component or context that persists across step unmounts rather than inside individual step components. For file uploads, keep the File object in parent state and only clear it after confirmed server-side success — never on upload failure.
ID: saas-error-handling.graceful-degradation.error-recovery-no-data-loss
Severity: high
What to look for: Focus on forms and multi-step workflows. When an error occurs (network failure, server error, validation rejection): (1) Does the form preserve the user's input in state, or does the error state reset the form fields? (2) Do multi-step wizards or onboarding flows preserve progress across errors? (3) Do "Try again" buttons re-submit with the same data, or do they clear state and require the user to re-enter everything? Look for patterns where catch blocks call resetForm() or setState({}) — these likely clear user data on error. Also check whether long-form inputs (rich text editors, file uploads) survive error conditions.
Pass criteria: Count all forms and multi-step flows in the project and test each for data preservation on error. Pass if form state is preserved on error (input fields retain their values after a failed submission), and retry mechanisms re-submit with the preserved data. At least 90% of forms must preserve state on error. Report the ratio: "X of Y forms preserve user input on error."
Fail criteria: Fail if form fields are cleared on error (user must re-type their input). Fail if error boundaries' reset() function causes parent state to reset, clearing form data. Fail if multi-step form progress is lost on any individual step error.
Skip (N/A) when: The application has no forms and no multi-step workflows. Signal: no <form> elements, no form library, no wizard or stepper components.
Detail on fail: Describe the data loss scenario (e.g., "Signup form's catch block calls form.reset() on API error — user loses all typed input; file attachment is cleared from state on upload failure"). Max 500 chars.
Remediation: Losing user data on error is the most frustrating failure mode. Users who have to re-enter a long form after a server error often abandon entirely.
Ensure error handling preserves form state:
// Wrong — clears user data on error
} catch (error) {
form.reset()
setError('Submission failed')
}
// Right — preserves user data, only sets error state
} catch (error) {
setSubmitError('Submission failed. Your data is preserved — please try again.')
setIsSubmitting(false)
}
For multi-step flows, store state in a parent component or context that persists across step re-renders, not in individual step components that unmount on error.
For file uploads, store the File object reference in state so users don't need to re-select after a failed upload:
// Keep the File in state; only clear after confirmed server-side success
const [selectedFile, setSelectedFile] = useState<File | null>(null)