Skip to content

Commit ec2780e

Browse files
committed
fix: add excerpt
1 parent 50d73d7 commit ec2780e

1 file changed

Lines changed: 69 additions & 69 deletions

File tree

src/blog/announcing-tanstack-form-v2-alpha.md

Lines changed: 69 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
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"
33
published: 2026-08-06
4-
excerpt: TanStack Form v2
4+
excerpt: The TanStack Form v2 alpha is here with more flexible validators, schema-oriented forms, safer form composition, and simpler SSR.
55
library: form
66
authors:
77
- Luca Jakob
@@ -29,12 +29,12 @@ Because v1 registered the validator once for each event, it could produce duplic
2929

3030
```ts title="v1"
3131
const form = useForm({
32-
defaultValues: { name: '' },
32+
defaultValues: { name: "" },
3333
validators: {
3434
onChange: mySchema,
3535
onBlur: mySchema,
3636
},
37-
})
37+
});
3838
```
3939

4040
#### After (v2)
@@ -43,14 +43,14 @@ In v2, you define the validator once and list both events in `triggers`. It can
4343

4444
```ts title="v2"
4545
const form = useForm({
46-
defaultValues: { name: '' },
46+
defaultValues: { name: "" },
4747
validators: [
4848
{
4949
run: mySchema,
50-
triggers: ['change', 'blur'],
50+
triggers: ["change", "blur"],
5151
},
5252
],
53-
})
53+
});
5454
```
5555

5656
### Multiple validators, one trigger
@@ -61,18 +61,18 @@ The opposite was awkward too. Say you want to run both a schema validator and a
6161

6262
```ts title="v1"
6363
const form = useForm({
64-
defaultValues: { name: '' },
64+
defaultValues: { name: "" },
6565
validators: {
6666
onChange: ({ formApi, value }) => {
67-
const errors = formApi.parseValuesWithSchema(mySchema)
67+
const errors = formApi.parseValuesWithSchema(mySchema);
6868

6969
// Stop if the schema validator found any errors.
70-
if (errors) return errors
70+
if (errors) return errors;
7171

72-
return checkReservedUsername(value)
72+
return checkReservedUsername(value);
7373
},
7474
},
75-
})
75+
});
7676
```
7777

7878
#### After (v2)
@@ -81,20 +81,20 @@ In v2, the two validators stay separate even though they share a trigger. Settin
8181

8282
```ts title="v2"
8383
const form = useForm({
84-
defaultValues: { name: '' },
84+
defaultValues: { name: "" },
8585
validators: [
8686
{
8787
run: mySchema,
88-
triggers: ['change'],
88+
triggers: ["change"],
8989
},
9090
{
9191
run: ({ value }) => checkReservedUsername(value),
92-
triggers: ['change'],
92+
triggers: ["change"],
9393
// Only run this check if the schema validator passes.
9494
bailIfInvalid: true,
9595
},
9696
],
97-
})
97+
});
9898
```
9999

100100
### Conditional validators
@@ -107,12 +107,12 @@ In v1, general conditions had to live inside the validator. The function still r
107107

108108
```ts title="v1"
109109
const form = useForm({
110-
defaultValues: { name: '' },
110+
defaultValues: { name: "" },
111111
validationLogic: revalidateLogic(),
112112
validators: {
113113
onDynamic: mySchema,
114114
},
115-
})
115+
});
116116
```
117117

118118
#### After (v2)
@@ -121,20 +121,20 @@ In v2, each trigger can include a `when` condition. The validator still runs on
121121

122122
```ts title="v2"
123123
const form = useForm({
124-
defaultValues: { name: '' },
124+
defaultValues: { name: "" },
125125
validators: [
126126
{
127127
run: schema,
128128
triggers: [
129129
{
130-
trigger: 'change',
130+
trigger: "change",
131131
// After the first submission attempt, validate every change.
132132
when: ({ formApi }) => formApi.state.submissionAttempts > 0,
133133
},
134134
],
135135
},
136136
],
137-
})
137+
});
138138
```
139139

140140
## Listeners rework
@@ -152,7 +152,7 @@ Consider an appointment form. Its schema requires a date, but we don't want to p
152152
```ts
153153
const schema = z.object({
154154
appointment: z.date(),
155-
})
155+
});
156156

157157
/*
158158
z.input<typeof schema> = {
@@ -176,10 +176,10 @@ const formOpts = formOptions({
176176
{
177177
// Error: The form is `appointment: null`, but the schema expects `Date`.
178178
run: schema,
179-
triggers: ['change'],
179+
triggers: ["change"],
180180
},
181181
],
182-
})
182+
});
183183
```
184184

185185
```ts title="Strict schema"
@@ -191,10 +191,10 @@ const formOpts = formOptions.strictSchema({
191191
validators: [
192192
{
193193
run: schema,
194-
triggers: ['change'],
194+
triggers: ["change"],
195195
},
196196
],
197-
})
197+
});
198198
```
199199

200200
```ts title="Loose schema"
@@ -206,10 +206,10 @@ const formOpts = formOptions.looseSchema({
206206
validators: [
207207
{
208208
run: schema,
209-
triggers: ['change'],
209+
triggers: ["change"],
210210
},
211211
],
212-
})
212+
});
213213
```
214214

215215
<!-- ::end:tabs -->
@@ -263,69 +263,69 @@ v1 configured server validation separately from the shared form options. Validat
263263
<!-- ::start:tabs variant="files" -->
264264

265265
```ts title="shared-code.ts"
266-
import { formOptions } from '@tanstack/react-form-nextjs'
266+
import { formOptions } from "@tanstack/react-form-nextjs";
267267

268268
export const formOpts = formOptions({
269269
defaultValues: { age: 0 },
270-
})
270+
});
271271
```
272272

273273
```ts title="action.ts"
274-
'use server'
274+
"use server";
275275

276276
import {
277277
createServerValidate,
278278
ServerValidateError,
279-
} from '@tanstack/react-form-nextjs'
280-
import { formOpts } from './shared-code'
279+
} from "@tanstack/react-form-nextjs";
280+
import { formOpts } from "./shared-code";
281281

282282
const mySchema = z.object({
283-
age: z.coerce.number().min(13, 'You must be 13 at least 13'),
284-
})
283+
age: z.coerce.number().min(13, "You must be 13 at least 13"),
284+
});
285285

286286
const serverValidate = createServerValidate({
287287
...formOpts,
288288
onServerValidate: mySchema,
289-
})
289+
});
290290

291291
export async function submit(_previous: unknown, formData: FormData) {
292292
try {
293-
const values = await serverValidate(formData)
293+
const values = await serverValidate(formData);
294294

295295
// Use values...
296296
} catch (error) {
297297
// The returned form state loses its inferred type after this check.
298298
if (error instanceof ServerValidateError) {
299-
return error.formState
299+
return error.formState;
300300
}
301301

302-
throw error
302+
throw error;
303303
}
304304
}
305305
```
306306

307307
```tsx title="client.tsx"
308-
'use client'
308+
"use client";
309309

310-
import { useActionState } from 'react'
310+
import { useActionState } from "react";
311311
import {
312312
initialFormState,
313313
mergeForm,
314314
useForm,
315315
useTransform,
316-
} from '@tanstack/react-form-nextjs'
317-
import { submit } from './action'
318-
import { formOpts } from './shared-code'
316+
} from "@tanstack/react-form-nextjs";
317+
import { submit } from "./action";
318+
import { formOpts } from "./shared-code";
319319

320320
export function Form() {
321-
const [state, action] = useActionState(submit, initialFormState)
321+
const [state, action] = useActionState(submit, initialFormState);
322322

323323
const form = useForm({
324324
...formOpts,
325325
transform: useTransform((baseForm) => mergeForm(baseForm, state!), [state]),
326-
})
326+
});
327327

328-
return <form action={action as never}>{/* form.Field components */}</form>
328+
return <form action={action as never}>{/* form.Field components */}</form>;
329329
}
330330
```
331331

@@ -338,68 +338,68 @@ v2 moves the server validator into the shared `formOpts`, so the same configurat
338338
<!-- ::start:tabs variant="files" -->
339339

340340
```ts title="shared-code.ts"
341-
import { formOptions } from '@tanstack/react-form'
341+
import { formOptions } from "@tanstack/react-form";
342342

343343
const mySchema = z.object({
344-
age: z.coerce.number().min(13, 'You must be 13 at least 13'),
345-
})
344+
age: z.coerce.number().min(13, "You must be 13 at least 13"),
345+
});
346346

347347
export const formOpts = formOptions({
348348
defaultValues: { age: 0 },
349349
validators: [
350350
{
351-
triggers: ['server'],
351+
triggers: ["server"],
352352
runOnSubmit: false,
353353
run: mySchema,
354354
},
355355
],
356-
})
356+
});
357357
```
358358

359359
```ts title="action.ts"
360-
'use server'
360+
"use server";
361361

362362
import {
363363
initialServerFormState,
364364
serverValidateHelper,
365-
} from '@tanstack/react-form'
366-
import { next } from '@tanstack/react-form-nextjs'
367-
import { formOpts } from './shared-code'
365+
} from "@tanstack/react-form";
366+
import { next } from "@tanstack/react-form-nextjs";
367+
import { formOpts } from "./shared-code";
368368

369369
const { createServerValidate } = serverValidateHelper({
370370
framework: next(),
371-
})
371+
});
372372

373-
const serverValidate = createServerValidate(formOpts)
373+
const serverValidate = createServerValidate(formOpts);
374374

375375
export async function submit(_previous: unknown, formData: FormData) {
376-
const result = await serverValidate(formData)
376+
const result = await serverValidate(formData);
377377

378378
// serverState keeps the type inferred from formOpts.
379-
if (!result.success) return result.serverState
379+
if (!result.success) return result.serverState;
380380

381381
// Use result.values...
382-
return initialServerFormState
382+
return initialServerFormState;
383383
}
384384
```
385385

386386
```tsx title="client.tsx"
387-
'use client'
387+
"use client";
388388

389-
import { useActionState } from 'react'
390-
import { initialServerFormState, useForm } from '@tanstack/react-form'
391-
import { submit } from './action'
392-
import { formOpts } from './shared-code'
389+
import { useActionState } from "react";
390+
import { initialServerFormState, useForm } from "@tanstack/react-form";
391+
import { submit } from "./action";
392+
import { formOpts } from "./shared-code";
393393

394394
export function Form() {
395-
const [serverState, action] = useActionState(submit, initialServerFormState)
395+
const [serverState, action] = useActionState(submit, initialServerFormState);
396396

397397
const form = useForm({
398398
...formOpts,
399399
serverState,
400-
})
400+
});
401401

402-
return <form action={action}>{/* form.Field components */}</form>
402+
return <form action={action}>{/* form.Field components */}</form>;
403403
}
404404
```
405405

0 commit comments

Comments
 (0)