Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e0bc9a9
chore: remove redundant parseIssues usage in examples
LeCarbonator Oct 16, 2026
6c2d577
BREAKING: deprecate parseIssues in favor of createErrorMap
LeCarbonator Oct 16, 2026
d8475da
feat: create internal error map factory fn
LeCarbonator Oct 17, 2026
0ee06ab
feat: add Awaitable helper type
LeCarbonator Oct 18, 2026
8eef3a5
feat: create new types of formOptions
LeCarbonator Oct 18, 2026
3c0a45e
refactor: rename submit error symbol
LeCarbonator Oct 18, 2026
03b6f86
feat: add formOptions interface
LeCarbonator Oct 19, 2026
fed7670
feat: add JSDoc to formOptions
LeCarbonator Oct 19, 2026
4cc0eee
feat: add events.public.ts
LeCarbonator Oct 19, 2026
21e8e53
broken: begin migration of form options in core
LeCarbonator Oct 19, 2026
e7263c2
chore: migrate and clean up unit tests
LeCarbonator Oct 19, 2026
a3ce054
refactor: fix tsconfig and internal form options
LeCarbonator Oct 20, 2026
05a9ec3
chore: migrate unit tests to new system part 2
LeCarbonator Oct 20, 2026
9de050f
chore: add createErrorMap to submit context
LeCarbonator Oct 20, 2026
db43977
chore: migrate unit tests for lifecycle
LeCarbonator Oct 20, 2026
ee7b366
chore: migrate field-state.spec.ts
LeCarbonator Oct 20, 2026
c889e7b
chore: migrate array-methods unit tests
LeCarbonator Oct 20, 2026
1c67c67
fix: satisfy knip and fix tsconfig
LeCarbonator Oct 20, 2026
0860a9d
fix: fix tsconfig for real this time
LeCarbonator Oct 20, 2026
9867aaa
ci: merge from branch unit-test-migrate
LeCarbonator Oct 21, 2026
026606e
chore: patch core tests to get testing started
LeCarbonator Oct 21, 2026
43b6a3e
fix: initial fixes for react-form
LeCarbonator Oct 21, 2026
0b2ee53
perf: reduce instantiations by half
LeCarbonator Oct 21, 2026
d51cd02
fix createValidator usage
LeCarbonator Oct 21, 2026
035cca9
fix: fix reuse of form options
LeCarbonator Oct 21, 2026
6316368
fix: allow proper autocomplete of triggers
LeCarbonator Oct 21, 2026
7aa3e9a
fix: fix lit-form
LeCarbonator Oct 23, 2026
5405da6
fix: NGL, I got sidetracked. Various fixes
LeCarbonator Oct 24, 2026
cd80d6f
fix types in core, react-form, lit-form
LeCarbonator Oct 25, 2026
e9eada4
fix: fix angular-form types
LeCarbonator Oct 25, 2026
6c6d983
fix: solve preact-form types
LeCarbonator Oct 25, 2026
87b2f7c
fix: resolve lit-form and svelte-form
LeCarbonator Oct 25, 2026
f516ed9
fix: resolve solid-form types
LeCarbonator Oct 25, 2026
33796f3
fix: resolve vue-form types
LeCarbonator Oct 25, 2026
ecac098
fix: resolve form-devtools types
LeCarbonator Oct 25, 2026
711553d
fix: resolve types in SSR packages
LeCarbonator Oct 25, 2026
255d04d
fix: fix examples types
LeCarbonator Oct 25, 2026
880876b
Merge origin/alpha into opts-refactor
LeCarbonator Oct 25, 2026
04f2cfb
chore: apply fixes
LeCarbonator Oct 26, 2026
1c81263
chore: add unit tests for fieldErrorTarget
LeCarbonator Oct 26, 2026
8f1b844
chore: add changeset
LeCarbonator Oct 26, 2026
f82f1d4
fix: tighten loose unit test check
LeCarbonator Oct 26, 2026
0eade7d
fix: reduce instantiations in form groups
LeCarbonator Oct 28, 2026
b708fd8
fix: clean up duplicate types and inference
LeCarbonator Oct 28, 2026
dbfe603
fix: adjust internal form options
LeCarbonator Oct 28, 2026
7e091ce
Merge branch 'alpha' into opts-refactor
LeCarbonator Oct 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changeset/clear-turtles-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
'@tanstack/angular-form': minor
'@tanstack/form-core': minor
'@tanstack/form-devtools': minor
'@tanstack/lit-form': minor
'@tanstack/preact-form': minor
'@tanstack/react-form': minor
'@tanstack/react-form-nextjs': minor
'@tanstack/react-form-start': minor
'@tanstack/solid-form': minor
'@tanstack/svelte-form': minor
'@tanstack/vue-form': minor
---

BREAKING: Schemas no longer automatically send copies of field errors to the form. Use `fieldErrorTarget: 'field-and-self'` on form or group validators.

`fieldErrorTarget` can now control where schemas process errors. It has three modes:

- `'field'` (default) - Field errors are only sent to the field it targets.
- `'field-and-self'` - Field errors are sent to the field it targets, and a copy is sent to the form array.
- `'self'` - Fields do not receive errors, only the form.
5 changes: 3 additions & 2 deletions examples/angular/field-groups/src/bound-field.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ export class BoundFieldComponent {
watchFields: this.fields().lowerBound
? [this.fields().lowerBound!]
: undefined,
run: ({ value, parseIssues }: any) => {
run: ({ value }: any) => {
const valueResult = boundsSchema.safeParse(value)
if (!valueResult.success) return parseIssues(valueResult.error.issues)
if (!valueResult.success) return valueResult.error.issues
const lowerName = this.fields().lowerBound
if (!lowerName) return
const lowerResult = boundsSchema.safeParse(
Expand All @@ -61,6 +61,7 @@ export class BoundFieldComponent {
if (lowerResult.success && valueResult.data < lowerResult.data) {
return 'Upper bound must be greater than lower bound'
}
return undefined
},
},
])
Expand Down
20 changes: 14 additions & 6 deletions examples/angular/large-form/src/shared-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,23 @@ export const peopleFormOptions = formOptions({
triggers: ['change'],
run: async ({ value, createErrorMap }) => {
const errors = createErrorMap()
if (!value.fullName) errors.fields.fullName = 'Full name is required'
if (!value.phone) errors.fields.phone = 'Phone is required'
if (!value.fullName) {
errors.addFieldError('fullName', 'Full name is required')
}
if (!value.phone) {
errors.addFieldError('phone', 'Phone is required')
}
if (!value.emergencyContact.fullName) {
errors.fields['emergencyContact.fullName'] =
'Emergency contact full name is required'
errors.addFieldError(
'emergencyContact.fullName',
'Emergency contact full name is required',
)
}
if (!value.emergencyContact.phone) {
errors.fields['emergencyContact.phone'] =
'Emergency contact phone is required'
errors.addFieldError(
'emergencyContact.phone',
'Emergency contact phone is required',
)
}
return errors
},
Expand Down
4 changes: 2 additions & 2 deletions examples/lit/field-groups/src/field-bounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ function upperBoundField(props: {
{
triggers: ['change'],
watchFields: ['lowerBound'],
run: ({ value, parseIssues }) => {
run: ({ value }) => {
const upper = boundsSchema.safeParse(value)
if (!upper.success) return parseIssues(upper.error.issues)
if (!upper.success) return upper.error.issues
const lower = boundsSchema.safeParse(
props.fields.getFieldValue('lowerBound'),
)
Expand Down
20 changes: 14 additions & 6 deletions examples/lit/large-form/src/shared-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,23 @@ export const peopleFormOptions = formOptions({
triggers: ['change'],
run: ({ value, createErrorMap }) => {
const errors = createErrorMap()
if (!value.fullName) errors.fields.fullName = 'Full name is required'
if (!value.phone) errors.fields.phone = 'Phone is required'
if (!value.fullName) {
errors.addFieldError('fullName', 'Full name is required')
}
if (!value.phone) {
errors.addFieldError('phone', 'Phone is required')
}
if (!value.emergencyContact.fullName) {
errors.fields['emergencyContact.fullName'] =
'Emergency contact full name is required'
errors.addFieldError(
'emergencyContact.fullName',
'Emergency contact full name is required',
)
}
if (!value.emergencyContact.phone) {
errors.fields['emergencyContact.phone'] =
'Emergency contact phone is required'
errors.addFieldError(
'emergencyContact.phone',
'Emergency contact phone is required',
)
}
return errors
},
Expand Down
4 changes: 2 additions & 2 deletions examples/preact/field-groups/src/fieldGroups/fieldBounds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ function UpperBoundFieldComponent(props: UpperBoundFieldProps) {
// `watchFields` also uses virtual names. The wrapper rewrites
// `lowerBound` to the caller's concrete path, such as `minPrice`.
watchFields: ['lowerBound'],
run: ({ value, parseIssues }) => {
run: ({ value }) => {
const upperBoundResult = boundsSchema.safeParse(value)

if (!upperBoundResult.success) {
return parseIssues(upperBoundResult.error.issues)
return upperBoundResult.error.issues
}

const lowerBoundResult = boundsSchema.safeParse(
Expand Down
16 changes: 10 additions & 6 deletions examples/preact/large-form/src/features/people/shared-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,22 @@ export const peopleFormOpts = appFormOptions({
const errors = createErrorMap()

if (!value.fullName) {
errors.fields.fullName = 'Full name is required'
errors.addFieldError('fullName', 'Full name is required')
}
if (!value.phone) {
errors.fields.phone = 'Phone is required'
errors.addFieldError('phone', 'Phone is required')
}
if (!value.emergencyContact.fullName) {
errors.fields['emergencyContact.fullName'] =
'Emergency contact full name is required'
errors.addFieldError(
'emergencyContact.fullName',
'Emergency contact full name is required',
)
}
if (!value.emergencyContact.phone) {
errors.fields['emergencyContact.phone'] =
'Emergency contact phone is required'
errors.addFieldError(
'emergencyContact.phone',
'Emergency contact phone is required',
)
}

return errors
Expand Down
4 changes: 2 additions & 2 deletions examples/react/basic/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ function App() {
firstName: '',
lastName: '',
},
onSubmit: ({ value, createValidationError }) => {
onSubmit: ({ value, createErrorMap }) => {
// Do something with form data
console.log(value)

// If your endpoint returned validation errors, pass them on
return createValidationError({
return createErrorMap({
fields: {
// Set field-specific errors from the form!
firstName: 'Name already exists',
Expand Down
4 changes: 2 additions & 2 deletions examples/react/field-groups/src/fieldGroups/fieldBounds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ function UpperBoundFieldComponent(props: UpperBoundFieldProps) {
// `watchFields` also uses virtual names. The wrapper rewrites
// `lowerBound` to the caller's concrete path, such as `minPrice`.
watchFields: ['lowerBound'],
run: ({ value, parseIssues }) => {
run: ({ value }) => {
const upperBoundResult = boundsSchema.safeParse(value)

if (!upperBoundResult.success) {
return parseIssues(upperBoundResult.error.issues)
return upperBoundResult.error.issues
}

const lowerBoundResult = boundsSchema.safeParse(
Expand Down
16 changes: 10 additions & 6 deletions examples/react/large-form/src/features/people/shared-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,22 @@ export const peopleFormOpts = appFormOptions({
const errors = createErrorMap()

if (!value.fullName) {
errors.fields.fullName = 'Full name is required'
errors.addFieldError('fullName', 'Full name is required')
}
if (!value.phone) {
errors.fields.phone = 'Phone is required'
errors.addFieldError('phone', 'Phone is required')
}
if (!value.emergencyContact.fullName) {
errors.fields['emergencyContact.fullName'] =
'Emergency contact full name is required'
errors.addFieldError(
'emergencyContact.fullName',
'Emergency contact full name is required',
)
}
if (!value.emergencyContact.phone) {
errors.fields['emergencyContact.phone'] =
'Emergency contact phone is required'
errors.addFieldError(
'emergencyContact.phone',
'Emergency contact phone is required',
)
}

return errors
Expand Down
15 changes: 5 additions & 10 deletions examples/react/next-server-actions-zod/src/app/action.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
'use server'

import {
initialServerFormState,
serverValidateHelper,
} from '@tanstack/react-form'
import { initialServerFormState } from '@tanstack/react-form'
import { next } from '@tanstack/react-form-nextjs'
import { formOpts } from './shared-code'

const { createServerValidate } = serverValidateHelper({
framework: next({
info: {
numbers: ['age'],
},
}),
const { createServerValidate } = next({
info: {
numbers: ['age'],
},
})

const serverValidate = createServerValidate(formOpts)
Expand Down
15 changes: 5 additions & 10 deletions examples/react/next-server-actions/src/app/action.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
'use server'

import {
initialServerFormState,
serverValidateHelper,
} from '@tanstack/react-form'
import { initialServerFormState } from '@tanstack/react-form'
import { next } from '@tanstack/react-form-nextjs'
import { formOpts } from './shared-code'

const { createServerValidate } = serverValidateHelper({
framework: next({
info: {
numbers: ['age'],
},
}),
const { createServerValidate } = next({
info: {
numbers: ['age'],
},
})

const serverValidate = createServerValidate(formOpts)
Expand Down
11 changes: 8 additions & 3 deletions examples/react/next-server-actions/src/app/shared-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export const formOpts = formOptions({
const errors = createErrorMap()

if (value.age < 8) {
errors.fields.age = 'Client validation: You must be at least 8'
errors.addFieldError(
'age',
'Client validation: You must be at least 8',
)
}

return errors
Expand All @@ -25,8 +28,10 @@ export const formOpts = formOptions({
const errors = createErrorMap()

if (value.age < 12) {
errors.fields.age =
'Server validation: You must be at least 12 to sign up'
errors.addFieldError(
'age',
'Server validation: You must be at least 12 to sign up',
)
}

return errors
Expand Down
15 changes: 5 additions & 10 deletions examples/react/nextjs/app/action.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
'use server'

import {
initialServerFormState,
serverValidateHelper,
} from '@tanstack/react-form'
import { initialServerFormState } from '@tanstack/react-form'
import { next } from '@tanstack/react-form-nextjs'
import { formOpts } from './shared-code'

const { createServerValidate } = serverValidateHelper({
framework: next({
info: {
numbers: ['age'],
},
}),
const { createServerValidate } = next({
info: {
numbers: ['age'],
},
})

const serverValidate = createServerValidate(formOpts)
Expand Down
15 changes: 12 additions & 3 deletions examples/react/nextjs/app/shared-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export const formOpts = formOptions({
const errors = createErrorMap()

if (value.age < 8) {
errors.fields.age = 'Client validation: You must be at least 8'
errors.addFieldError(
'age',
'Client validation: You must be at least 8',
)
}

return errors
Expand All @@ -26,11 +29,17 @@ export const formOpts = formOptions({
const errors = createErrorMap()

if (!value.firstName.trim()) {
errors.fields.firstName = 'Server validation: First name is required'
errors.addFieldError(
'firstName',
'Server validation: First name is required',
)
}

if (value.age < 13) {
errors.fields.age = 'Server validation: You must be at least 13'
errors.addFieldError(
'age',
'Server validation: You must be at least 13',
)
}

return errors
Expand Down
15 changes: 12 additions & 3 deletions examples/react/tanstack-start/src/utils/form-isomorphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export const formOpts = formOptions({
const errors = createErrorMap()

if (value.age < 8) {
errors.fields.age = 'Client validation: You must be at least 8'
errors.addFieldError(
'age',
'Client validation: You must be at least 8',
)
}

return errors
Expand All @@ -26,11 +29,17 @@ export const formOpts = formOptions({
const errors = createErrorMap()

if (!value.firstName.trim()) {
errors.fields.firstName = 'Server validation: First name is required'
errors.addFieldError(
'firstName',
'Server validation: First name is required',
)
}

if (value.age < 13) {
errors.fields.age = 'Server validation: You must be at least 13'
errors.addFieldError(
'age',
'Server validation: You must be at least 13',
)
}

return errors
Expand Down
5 changes: 1 addition & 4 deletions examples/react/tanstack-start/src/utils/form-validator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { serverValidateHelper } from '@tanstack/react-form'
import { start } from '@tanstack/react-form-start'

export const { createServerValidate } = serverValidateHelper({
framework: start(),
})
export const { createServerValidate } = start()
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createValidator } from '@tanstack/react-form'
import { bookingFormSchema } from './schema'
import type { ReactNode } from 'react'
import type { ReactFormType } from '@tanstack/react-form'
import type { ReactNode } from 'react'
import { appFormOptions } from '@/components/form/app-form'
import { FieldGroup } from '@/components/ui/field'

Expand Down
Loading
Loading