Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion packages/form-core/src/FormApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1589,7 +1589,13 @@ export class FormApi<
) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const fieldInstance = this.fieldInfo[field]?.instance
if (!fieldInstance) return []

if (!fieldInstance) {
// If there's no field instance, we still need to run form-level validation
// to ensure fields without components can still be validated
this.validate(cause)
return []
}

// If the field is not touched (same logic as in validateAllFields)
if (!fieldInstance.state.meta.isTouched) {
Expand Down
86 changes: 86 additions & 0 deletions packages/form-core/tests/setFieldValue-validation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { describe, expect, it } from 'vitest'
import { FormApi } from '../src/FormApi'

describe('setFieldValue validation for fields without components', () => {
it('should validate field when setFieldValue is called even without field component', () => {
const form = new FormApi({
defaultValues: {
name: '',
},
validators: {
onChange: ({ value }) => {
if (!value.name) {
return {
fields: {
name: 'Name is required',
},
}
}
return undefined
},
},
})

form.mount()

// Initially, form should be valid (no validation errors yet)
expect(form.state.isValid).toBe(true)
expect(form.state.errors).toEqual([])

// Set field value to empty string (should trigger validation and show error)
form.setFieldValue('name', '')

// Form should now be invalid due to validation error
expect(form.state.isValid).toBe(false)
expect(form.state.fieldMeta.name?.errors).toEqual(['Name is required'])

// Set field value to valid value (should clear validation error)
form.setFieldValue('name', 'John')

// Form should now be valid again
expect(form.state.isValid).toBe(true)
expect(form.state.fieldMeta.name?.errors).toEqual([])
})

it('should validate field with form-level async validator', async () => {
const form = new FormApi({
defaultValues: {
email: '',
},
validators: {
onChangeAsync: async ({ value }) => {
if (!value.email) {
return {
fields: {
email: 'Email is required',
},
}
}
return undefined
},
},
})

form.mount()

// Set field value to empty string (should trigger async validation)
form.setFieldValue('email', '')

// Wait for async validation to complete
await new Promise((resolve) => setTimeout(resolve, 100))

// Form should now be invalid due to validation error
expect(form.state.isValid).toBe(false)
expect(form.state.fieldMeta.email?.errors).toEqual(['Email is required'])

// Set field value to valid value (should clear validation error)
form.setFieldValue('email', 'john@example.com')

// Wait for async validation to complete
await new Promise((resolve) => setTimeout(resolve, 100))

// Form should now be valid again
expect(form.state.isValid).toBe(true)
expect(form.state.fieldMeta.email?.errors).toEqual([])
})
})
Loading