Skip to content

Commit 2f0a23f

Browse files
docs: adding custom errors docs (#1215)
* docs: adding custom errors docs * docs: updating docs according to feedback * ci: apply automated fixes and generate docs * docs: updating docs * ci: apply automated fixes and generate docs * docs: updating docs * ci: apply automated fixes and generate docs * docs: rephrase initial paragraph --------- Co-authored-by: Leonardo Montini <[email protected]>
1 parent 26b3ddd commit 2f0a23f

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@
109109
"label": "Listeners",
110110
"to": "framework/react/guides/listeners"
111111
},
112+
{
113+
"label": "Custom Errors",
114+
"to": "framework/react/guides/custom-errors"
115+
},
112116
{
113117
"label": "Submission Handling",
114118
"to": "framework/react/guides/submission-handling"
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
---
2+
id: custom-errors
3+
title: Custom Errors
4+
---
5+
6+
TanStack Form provides complete flexibility in the types of error values you can return from validators. String errors are the most common and easy to work with, but the library allows you to return any type of value from your validators.
7+
8+
As a general rule, any truthy value is considered as an error and will mark the form or field as invalid, while falsy values (`false`, `undefined`, `null`, etc..) mean there is no error, the form or field is valid.
9+
10+
## Return String Values from Forms
11+
12+
```tsx
13+
<form.Field
14+
name="username"
15+
validators={{
16+
onChange: ({ value }) =>
17+
value.length < 3 ? 'Username must be at least 3 characters' : undefined,
18+
}}
19+
/>
20+
```
21+
22+
For form-level validation affecting multiple fields:
23+
24+
```tsx
25+
const form = useForm({
26+
defaultValues: {
27+
username: '',
28+
email: '',
29+
},
30+
validators: {
31+
onChange: ({ value }) => {
32+
return {
33+
fields: {
34+
username:
35+
value.username.length < 3 ? 'Username too short' : undefined,
36+
email: !value.email.includes('@') ? 'Invalid email' : undefined,
37+
},
38+
}
39+
},
40+
},
41+
})
42+
```
43+
44+
String errors are the most common type and are easily displayed in your UI:
45+
46+
```tsx
47+
{
48+
field.state.meta.errors.map((error, i) => (
49+
<div key={i} className="error">
50+
{error}
51+
</div>
52+
))
53+
}
54+
```
55+
56+
### Numbers
57+
58+
Useful for representing quantities, thresholds, or magnitudes:
59+
60+
```tsx
61+
<form.Field
62+
name="age"
63+
validators={{
64+
onChange: ({ value }) => (value < 18 ? 18 - value : undefined),
65+
}}
66+
/>
67+
```
68+
69+
Display in UI:
70+
71+
```tsx
72+
{
73+
/* TypeScript knows the error is a number based on your validator */
74+
}
75+
;<div className="error">
76+
You need {field.state.meta.errors[0]} more years to be eligible
77+
</div>
78+
```
79+
80+
### Booleans
81+
82+
Simple flags to indicate error state:
83+
84+
```tsx
85+
<form.Field
86+
name="accepted"
87+
validators={{
88+
onChange: ({ value }) => (!value ? true : undefined),
89+
}}
90+
/>
91+
```
92+
93+
Display in UI:
94+
95+
```tsx
96+
{
97+
field.state.meta.errors[0] === true && (
98+
<div className="error">You must accept the terms</div>
99+
)
100+
}
101+
```
102+
103+
### Objects
104+
105+
Rich error objects with multiple properties:
106+
107+
```tsx
108+
<form.Field
109+
name="email"
110+
validators={{
111+
onChange: ({ value }) => {
112+
if (!value.includes('@')) {
113+
return {
114+
message: 'Invalid email format',
115+
severity: 'error',
116+
code: 1001,
117+
}
118+
}
119+
return undefined
120+
},
121+
}}
122+
/>
123+
```
124+
125+
Display in UI:
126+
127+
```tsx
128+
{
129+
typeof field.state.meta.errors[0] === 'object' && (
130+
<div className={`error ${field.state.meta.errors[0].severity}`}>
131+
{field.state.meta.errors[0].message}
132+
<small> (Code: {field.state.meta.errors[0].code})</small>
133+
</div>
134+
)
135+
}
136+
```
137+
138+
in the example above it depends on the event error you want to display.
139+
140+
### Arrays
141+
142+
Multiple error messages for a single field:
143+
144+
```tsx
145+
<form.Field
146+
name="password"
147+
validators={{
148+
onChange: ({ value }) => {
149+
const errors = []
150+
if (value.length < 8) errors.push('Password too short')
151+
if (!/[A-Z]/.test(value)) errors.push('Missing uppercase letter')
152+
if (!/[0-9]/.test(value)) errors.push('Missing number')
153+
154+
return errors.length ? errors : undefined
155+
},
156+
}}
157+
/>
158+
```
159+
160+
Display in UI:
161+
162+
```tsx
163+
{
164+
Array.isArray(field.state.meta.errors) && (
165+
<ul className="error-list">
166+
{field.state.meta.errors.map((err, i) => (
167+
<li key={i}>{err}</li>
168+
))}
169+
</ul>
170+
)
171+
}
172+
```
173+
174+
## The `disableErrorFlat` Prop on Fields
175+
176+
By default, TanStack Form flattens errors from all validation sources (onChange, onBlur, onSubmit) into a single `errors` array. The `disableErrorFlat` prop preserves the error sources:
177+
178+
```tsx
179+
<form.Field
180+
name="email"
181+
disableErrorFlat
182+
validators={{
183+
onChange: ({ value }) =>
184+
!value.includes('@') ? 'Invalid email format' : undefined,
185+
onBlur: ({ value }) =>
186+
!value.endsWith('.com') ? 'Only .com domains allowed' : undefined,
187+
onSubmit: ({ value }) => (value.length < 5 ? 'Email too short' : undefined),
188+
}}
189+
/>
190+
```
191+
192+
Without `disableErrorFlat`, all errors would be combined into `field.state.meta.errors`. With it, you can access errors by their source:
193+
194+
```tsx
195+
{
196+
field.state.meta.errorMap.onChange && (
197+
<div className="real-time-error">{field.state.meta.errorMap.onChange}</div>
198+
)
199+
}
200+
201+
{
202+
field.state.meta.errorMap.onBlur && (
203+
<div className="blur-feedback">{field.state.meta.errorMap.onBlur}</div>
204+
)
205+
}
206+
207+
{
208+
field.state.meta.errorMap.onSubmit && (
209+
<div className="submit-error">{field.state.meta.errorMap.onSubmit}</div>
210+
)
211+
}
212+
```
213+
214+
This is useful for:
215+
216+
- Displaying different types of errors with different UI treatments
217+
- Prioritizing errors (e.g., showing submission errors more prominently)
218+
- Implementing progressive disclosure of errors
219+
220+
## Type Safety of `errors` and `errorMap`
221+
222+
TanStack Form provides strong type safety for error handling. Each key in the `errorMap` has exactly the type returned by its corresponding validator, while the `errors` array contains a union type of all the possible error values from all validators:
223+
224+
```tsx
225+
<form.Field
226+
name="password"
227+
validators={{
228+
onChange: ({ value }) => {
229+
// This returns a string or undefined
230+
return value.length < 8 ? 'Too short' : undefined
231+
},
232+
onBlur: ({ value }) => {
233+
// This returns an object or undefined
234+
if (!/[A-Z]/.test(value)) {
235+
return { message: 'Missing uppercase', level: 'warning' }
236+
}
237+
return undefined
238+
},
239+
}}
240+
children={(field) => {
241+
// TypeScript knows that errors[0] can be string | { message: string, level: string } | undefined
242+
const error = field.state.meta.errors[0]
243+
244+
// Type-safe error handling
245+
if (typeof error === 'string') {
246+
return <div className="string-error">{error}</div>
247+
} else if (error && typeof error === 'object') {
248+
return <div className={error.level}>{error.message}</div>
249+
}
250+
251+
return null
252+
}}
253+
/>
254+
```
255+
256+
The `errorMap` property is also fully typed, matching the return types of your validation functions:
257+
258+
```tsx
259+
// With disableErrorFlat
260+
<form.Field
261+
name="email"
262+
disableErrorFlat
263+
validators={{
264+
onChange: ({ value }): string | undefined =>
265+
!value.includes("@") ? "Invalid email" : undefined,
266+
onBlur: ({ value }): { code: number, message: string } | undefined =>
267+
!value.endsWith(".com") ? { code: 100, message: "Wrong domain" } : undefined
268+
}}
269+
children={(field) => {
270+
// TypeScript knows the exact type of each error source
271+
const onChangeError: string | undefined = field.state.meta.errorMap.onChange;
272+
const onBlurError: { code: number, message: string } | undefined = field.state.meta.errorMap.onBlur;
273+
274+
return (/* ... */);
275+
}}
276+
/>
277+
```
278+
279+
This type safety helps catch errors at compile time instead of runtime, making your code more reliable and maintainable.

0 commit comments

Comments
 (0)