You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/blog/announcing-tanstack-form-v2-alpha.md
+78-76Lines changed: 78 additions & 76 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,5 @@
1
1
---
2
-
title: 'Form v2 is here: All you need to know about the alpha'
2
+
title: "Form v2 is here: All you need to know about the alpha"
3
3
published: 2026-08-06
4
4
excerpt: The TanStack Form v2 alpha is here with more flexible validators, schema-oriented forms, safer form composition, and simpler SSR.
5
5
library: form
@@ -21,120 +21,120 @@ v2 uses a pipeline instead. Each validator gets its own entry and declares the e
21
21
22
22
### One validator, multiple triggers
23
23
24
-
####Before (v1)
24
+
### Before (v1)
25
25
26
26
Say you want to validate a field when its value changes and when it loses focus. v1 couldn't attach one validator to both events directly, so you had to add the same validator twice.
27
27
28
28
Because v1 registered the validator once for each event, it could produce duplicate errors. Your app then had to remove those duplicates before showing them in the UI.
29
29
30
30
```ts title="v1"
31
31
const form =useForm({
32
-
defaultValues: { name: '' },
32
+
defaultValues: { name: "" },
33
33
validators: {
34
34
onChange: mySchema,
35
35
onBlur: mySchema,
36
36
},
37
-
})
37
+
});
38
38
```
39
39
40
-
####After (v2)
40
+
### After (v2)
41
41
42
42
In v2, you define the validator once and list both events in `triggers`. It can run on change and blur without duplicating its setup or its errors.
43
43
44
44
```ts title="v2"
45
45
const form =useForm({
46
-
defaultValues: { name: '' },
46
+
defaultValues: { name: "" },
47
47
validators: [
48
48
{
49
49
run: mySchema,
50
-
triggers: ['change', 'blur'],
50
+
triggers: ["change", "blur"],
51
51
},
52
52
],
53
-
})
53
+
});
54
54
```
55
55
56
56
### Multiple validators, one trigger
57
57
58
-
####Before (v1)
58
+
### Before (v1)
59
59
60
60
The opposite was awkward too. Say you want to run both a schema validator and a reserved-username check whenever a field changes. v1 only had one `onChange` key, so you had to wrap both validators in a single callback and control how they ran yourself:
In v2, the two validators stay separate even though they share a trigger. Setting `bailIfInvalid` on the username check makes it run only if the schema validator passes, just like the early return in the v1 example.
81
81
82
82
```ts title="v2"
83
83
const form =useForm({
84
-
defaultValues: { name: '' },
84
+
defaultValues: { name: "" },
85
85
validators: [
86
86
{
87
87
run: mySchema,
88
-
triggers: ['change'],
88
+
triggers: ["change"],
89
89
},
90
90
{
91
91
run: ({ value }) =>checkReservedUsername(value),
92
-
triggers: ['change'],
92
+
triggers: ["change"],
93
93
// Only run this check if the schema validator passes.
94
94
bailIfInvalid: true,
95
95
},
96
96
],
97
-
})
97
+
});
98
98
```
99
99
100
100
### Conditional validators
101
101
102
102
Sometimes you only want an event to trigger validation after something else has happened. One common React Hook Form pattern is to validate on submit first, then validate on every change after the first submission attempt.
103
103
104
-
####Before (v1)
104
+
### Before (v1)
105
105
106
106
In v1, general conditions had to live inside the validator. The function still ran on every change, only to return early while the condition was false. For this particular submit-then-change pattern, v1 also offered `onDynamic` together with `revalidateLogic()`:
107
107
108
108
```ts title="v1"
109
109
const form =useForm({
110
-
defaultValues: { name: '' },
110
+
defaultValues: { name: "" },
111
111
validationLogic: revalidateLogic(),
112
112
validators: {
113
113
onDynamic: mySchema,
114
114
},
115
-
})
115
+
});
116
116
```
117
117
118
-
####After (v2)
118
+
### After (v2)
119
119
120
120
In v2, each trigger can include a `when` condition. The validator still runs on submit, but the change trigger only becomes active after the first submission attempt. Until then, changes don't call the validator at all. The condition now sits next to the trigger it controls, with no early return inside the validator or separate validation setting.
121
121
122
122
```ts title="v2"
123
123
const form =useForm({
124
-
defaultValues: { name: '' },
124
+
defaultValues: { name: "" },
125
125
validators: [
126
126
{
127
127
run: schema,
128
128
triggers: [
129
129
{
130
-
trigger: 'change',
130
+
trigger: "change",
131
131
// After the first submission attempt, validate every change.
Form composition made it possible to bundle reusable components with a field and reduced the boilerplate needed to build forms. In v1, however, those components weren't restricted by the field's value type. A string field such as `email` could render a `NumberInput` without any warning about the mismatch:
222
222
@@ -233,7 +233,7 @@ Form composition made it possible to bundle reusable components with a field and
233
233
</form.AppField>
234
234
```
235
235
236
-
####After (v2)
236
+
### After (v2)
237
237
238
238
v2 lets composed field components be branded with the value types they support. Once `email` is inferred as a string field, incompatible components are left out of its field API. Trying to access `field.NumberInput` therefore produces a type error before the form reaches the browser.
239
239
@@ -263,69 +263,70 @@ v1 configured server validation separately from the shared form options. Validat
0 commit comments