Validate before submit
Controlled fields, a per-field error map, and a guarded submit.
From frontend-patterns, “Controlled Form with Validation”.
Submit an empty form
Press Submit both, then fill the fields and retry.
Server decidesBad
requests: 0validate() firstGood
requests: 0errors: {}const validate = (): boolean => {
const newErrors: FormErrors = {}
if (!formData.name.trim()) newErrors.name = 'Name is required'
else if (formData.name.length > 200) newErrors.name = 'Name must be under 200 characters'
if (!formData.description.trim()) newErrors.description = 'Description is required'
if (!formData.endDate) newErrors.endDate = 'End date is required'
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!validate()) return
await createMarket(formData)
}
Whitespace and length
Type a name or pick a preset.
0/200
Naive checkBad
if (!name) errors.name = 'Name is required'trim() + max lengthGood
!name.trim()name.length > 200if (!formData.name.trim()) {
newErrors.name = 'Name is required'
} else if (formData.name.length > 200) {
newErrors.name = 'Name must be under 200 characters'
}
When createMarket throws
Press Create in each, then untick the failure.
await, no catchBad
await createMarket(formData)try / catch / finallyGood
catch → setSubmitError(…)setSubmitting(true)
try {
await createMarket(formData)
} catch (error) {
setSubmitError('Could not create the market. Try again.')
} finally {
setSubmitting(false)
}
Why it matters, pros and cons
Why it matters
A client check is instant and names the field. A caught submit becomes UI state.
Pros
- Every error shows beside its field.
- No requests the server would reject.
validate()is pure and easy to test.
Cons
- Not security: the server must validate again.
- Errors refresh only on submit unless you revalidate on change.
- Native
requiredmay be enough for one field.