Added AI skills

This commit is contained in:
Jose Selesan
2026-09-04 16:49:24 -03:00
parent 778f3fdcad
commit dfa7f73ebb
368 changed files with 51560 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
---
title: Surface Server Errors via setError('root.serverError', ...)
impact: HIGH
impactDescription: prevents lost server-side validation errors and unrecoverable form state
tags: valid, server-errors, setError, async, error-handling
---
## Surface Server Errors via setError('root.serverError', ...)
`handleSubmit` does not catch errors thrown inside async submit handlers — it logs them and silently leaves the form unrecoverable (`isSubmitting` stays `true` if you `throw`). The canonical pattern is to `try/catch` inside the submit handler and route API failures into `setError`. Use field-level `setError(name, ...)` when the server tells you which field is wrong; use `setError('root.serverError', ...)` for general failures (network error, 500, "Account is locked").
**Incorrect (server error is thrown, swallowed, and form is now stuck):**
```typescript
function LoginForm() {
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<LoginFormData>()
const onSubmit = async (data: LoginFormData) => {
const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify(data) })
if (!res.ok) throw new Error('Login failed') // Lost: no UI feedback, isSubmitting stuck
redirect('/dashboard')
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} />
<input type="password" {...register('password')} />
<button disabled={isSubmitting}>Sign in</button>
</form>
)
}
```
**Correct (server errors surfaced via setError, form stays recoverable):**
```typescript
function LoginForm() {
const {
register,
handleSubmit,
setError,
clearErrors,
formState: { errors, isSubmitting },
} = useForm<LoginFormData>()
const onSubmit = async (data: LoginFormData) => {
clearErrors('root.serverError')
try {
const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify(data) })
if (!res.ok) {
const body = await res.json()
if (body.field === 'password') {
setError('password', { type: 'server', message: body.message })
} else {
setError('root.serverError', { type: 'server', message: body.message ?? 'Sign in failed' })
}
return
}
redirect('/dashboard')
} catch {
setError('root.serverError', { type: 'network', message: 'Network error — please retry' })
}
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{errors.root?.serverError && (
<div role="alert">{errors.root.serverError.message}</div>
)}
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input type="password" {...register('password')} />
{errors.password && <span>{errors.password.message}</span>}
<button disabled={isSubmitting}>Sign in</button>
</form>
)
}
```
**Key details:**
- Root-level errors live under `errors.root.{key}` — pick any key (`serverError`, `network`, `rateLimit`) and read it back the same way
- Root errors **persist across submissions** until you call `clearErrors('root.serverError')` — clear at the start of each submit, or rely on the next resolver pass to overwrite
- Always `try/catch` async submit handlers. `handleSubmit` will not surface thrown errors, and `isSubmitting` only resets when the handler returns (resolves), not when it throws — see also `formstate-async-submit-lifecycle`
Reference: [setError](https://react-hook-form.com/docs/useform/seterror) · [Discussion #9691 — Handle global/server errors](https://github.com/orgs/react-hook-form/discussions/9691)