# Error Handling Guide Complete guide for handling and displaying form errors. --- ## Error Display Patterns ### 1. Inline Errors (Recommended) ```typescript {errors.email && ( {errors.email.message} )} ``` ### 2. Error Summary (Accessibility Best Practice) ```typescript {Object.keys(errors).length > 0 && (

Please fix the following errors:

)} ``` ### 3. Toast Notifications ```typescript const onError = (errors) => { toast.error(`Please fix ${Object.keys(errors).length} errors`) }
``` --- ## ARIA Attributes ### Required Attributes ```typescript {errors.email && ( {errors.email.message} )} ``` --- ## Custom Error Messages ### Method 1: In Zod Schema ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript { register('email').onChange(e) clearErrors('email') // Clear error when user starts typing }} /> ``` ### Clear All Errors on Submit Success ```typescript const onSubmit = async (data) => { const success = await submitData(data) if (success) { reset() // Clears form and errors } } ``` --- ## Internationalization (i18n) ```typescript 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 ```typescript function FormError({ error }: { error?: FieldError }) { if (!error) return null return (
... {error.message}
) } // Usage ``` ### Field Group with Error ```typescript function FieldGroup({ name, label, type = 'text', register, errors }) { return (
{errors[name] && }
) } ``` --- **Official Docs**: https://react-hook-form.com/