4.8 KiB
4.8 KiB
Error Handling Guide
Complete guide for handling and displaying form errors.
Error Display Patterns
1. Inline Errors (Recommended)
<input {...register('email')} />
{errors.email && (
<span role="alert" className="text-red-600">
{errors.email.message}
</span>
)}
2. Error Summary (Accessibility Best Practice)
{Object.keys(errors).length > 0 && (
<div role="alert" aria-live="assertive" className="error-summary">
<h3>Please fix the following errors:</h3>
<ul>
{Object.entries(errors).map(([field, error]) => (
<li key={field}>
<strong>{field}:</strong> {error.message}
</li>
))}
</ul>
</div>
)}
3. Toast Notifications
const onError = (errors) => {
toast.error(`Please fix ${Object.keys(errors).length} errors`)
}
<form onSubmit={handleSubmit(onSubmit, onError)}>
ARIA Attributes
Required Attributes
<input
{...register('email')}
aria-invalid={errors.email ? 'true' : 'false'}
aria-describedby={errors.email ? 'email-error' : undefined}
aria-required="true"
/>
{errors.email && (
<span id="email-error" role="alert">
{errors.email.message}
</span>
)}
Custom Error Messages
Method 1: In Zod Schema
const schema = z.object({
email: z.string()
.min(1, 'Email is required')
.email('Please enter a valid email address'),
password: z.string()
.min(8, { message: 'Password must be at least 8 characters long' }),
})
Method 2: Custom Error Map
const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
switch (issue.code) {
case z.ZodIssueCode.too_small:
return { message: `Must be at least ${issue.minimum} characters` }
case z.ZodIssueCode.invalid_string:
if (issue.validation === 'email') {
return { message: 'Please enter a valid email address' }
}
break
default:
return { message: ctx.defaultError }
}
}
z.setErrorMap(customErrorMap)
Error Formatting
Flatten Errors for Forms
try {
schema.parse(data)
} catch (error) {
if (error instanceof z.ZodError) {
const formattedErrors = error.flatten().fieldErrors
// Result: { email: ['Invalid email'], password: ['Too short'] }
}
}
Format Errors for Display
const formatError = (error: FieldError): string => {
switch (error.type) {
case 'required':
return 'This field is required'
case 'min':
return `Minimum length is ${error.message}`
case 'pattern':
return 'Invalid format'
default:
return error.message || 'Invalid value'
}
}
Server Error Integration
const onSubmit = async (data) => {
try {
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data),
})
const result = await response.json()
if (!result.success && result.errors) {
// Map server errors to form fields
Object.entries(result.errors).forEach(([field, message]) => {
setError(field, {
type: 'server',
message: Array.isArray(message) ? message[0] : message,
})
})
}
} catch (error) {
// Network error
setError('root', {
type: 'server',
message: 'Unable to connect. Please try again.',
})
}
}
Error Persistence
Clear Errors on Input Change
<input
{...register('email')}
onChange={(e) => {
register('email').onChange(e)
clearErrors('email') // Clear error when user starts typing
}}
/>
Clear All Errors on Submit Success
const onSubmit = async (data) => {
const success = await submitData(data)
if (success) {
reset() // Clears form and errors
}
}
Internationalization (i18n)
import { useTranslation } from 'react-i18next'
const { t } = useTranslation()
const schema = z.object({
email: z.string().email(t('errors.invalidEmail')),
password: z.string().min(8, t('errors.passwordTooShort')),
})
Error Components
Reusable Error Display
function FormError({ error }: { error?: FieldError }) {
if (!error) return null
return (
<div role="alert" className="error">
<svg className="icon">...</svg>
<span>{error.message}</span>
</div>
)
}
// Usage
<FormError error={errors.email} />
Field Group with Error
function FieldGroup({ name, label, type = 'text', register, errors }) {
return (
<div className="field-group">
<label htmlFor={name}>{label}</label>
<input
id={name}
type={type}
{...register(name)}
aria-invalid={errors[name] ? 'true' : 'false'}
/>
{errors[name] && <FormError error={errors[name]} />}
</div>
)
}
Official Docs: https://react-hook-form.com/