diff --git a/docs/reference/classes/formapi.md b/docs/reference/classes/formapi.md index 5fc6231e3..e12c7d4d5 100644 --- a/docs/reference/classes/formapi.md +++ b/docs/reference/classes/formapi.md @@ -153,6 +153,57 @@ Defined in: [packages/form-core/src/FormApi.ts:1661](https://github.com/TanStack *** +### getAllErrors() + +```ts +getAllErrors(): object +``` + +Defined in: [packages/form-core/src/FormApi.ts:1872](https://github.com/TanStack/form/blob/main/packages/form-core/src/FormApi.ts#L1872) + +Returns form and field level errors + +#### Returns + +`object` + +##### fields + +```ts +fields: Record, { + errorMap: ValidationErrorMap; + errors: unknown[]; +}>; +``` + +##### form + +```ts +form: object; +``` + +###### form.errorMap + +```ts +errorMap: FormValidationErrorMap, UnwrapFormValidateOrFn, UnwrapFormAsyncValidateOrFn, UnwrapFormValidateOrFn, UnwrapFormAsyncValidateOrFn, UnwrapFormValidateOrFn, UnwrapFormAsyncValidateOrFn, UnwrapFormAsyncValidateOrFn>; +``` + +###### form.errors + +```ts +errors: ( + | UnwrapFormValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormAsyncValidateOrFn)[]; +``` + +*** + ### getFieldInfo() ```ts diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index e4810fd7f..a599b8cd5 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -1865,6 +1865,64 @@ export class FormApi< }) as never, ) } + + /** + * Returns form and field level errors + */ + getAllErrors = (): { + form: { + errors: Array< + | UnwrapFormValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormValidateOrFn + | UnwrapFormAsyncValidateOrFn + | UnwrapFormAsyncValidateOrFn + > + errorMap: FormValidationErrorMap< + UnwrapFormValidateOrFn, + UnwrapFormValidateOrFn, + UnwrapFormAsyncValidateOrFn, + UnwrapFormValidateOrFn, + UnwrapFormAsyncValidateOrFn, + UnwrapFormValidateOrFn, + UnwrapFormAsyncValidateOrFn, + UnwrapFormAsyncValidateOrFn + > + } + fields: Record< + DeepKeys, + { errors: ValidationError[]; errorMap: ValidationErrorMap } + > + } => { + return { + form: { + errors: this.state.errors, + errorMap: this.state.errorMap, + }, + fields: Object.entries(this.state.fieldMeta).reduce( + (acc, [fieldName, fieldMeta]) => { + if ( + Object.keys(fieldMeta as AnyFieldMeta).length && + (fieldMeta as AnyFieldMeta).errors.length + ) { + acc[fieldName as DeepKeys] = { + errors: (fieldMeta as AnyFieldMeta).errors, + errorMap: (fieldMeta as AnyFieldMeta).errorMap, + } + } + + return acc + }, + {} as Record< + DeepKeys, + { errors: ValidationError[]; errorMap: ValidationErrorMap } + >, + ), + } + } } function normalizeError(rawError?: FormValidationError): { diff --git a/packages/form-core/tests/FormApi.spec.ts b/packages/form-core/tests/FormApi.spec.ts index a7dd0ea54..73197daea 100644 --- a/packages/form-core/tests/FormApi.spec.ts +++ b/packages/form-core/tests/FormApi.spec.ts @@ -1462,6 +1462,61 @@ describe('form api', () => { }) }) + it('should return all errors', () => { + const form = new FormApi({ + defaultValues: { + name: 'other', + age: 'hi', + }, + validators: { + onChange: ({ value }) => { + if (value.name === 'other') return 'onChange - form' + return + }, + onMount: ({ value }) => { + if (value.name === 'other') return 'onMount - form' + return + }, + }, + }) + const field = new FieldApi({ + form, + name: 'name', + validators: { + onChange: ({ value }) => { + if (value === 'other') { + return 'onChange - field' + } + return + }, + }, + }) + + form.mount() + field.mount() + expect(form.getAllErrors()).toEqual({ + fields: {}, + form: { + errors: ['onMount - form'], + errorMap: { onMount: 'onMount - form' }, + }, + }) + + field.setValue('other') + expect(form.getAllErrors()).toEqual({ + fields: { + name: { + errors: ['onChange - field'], + errorMap: { onChange: 'onChange - field' }, + }, + }, + form: { + errors: ['onChange - form'], + errorMap: { onChange: 'onChange - form' }, + }, + }) + }) + it('should reset onChange errors when the issue is resolved', () => { const form = new FormApi({ defaultValues: { diff --git a/packages/form-core/tests/FormApi.test-d.ts b/packages/form-core/tests/FormApi.test-d.ts new file mode 100644 index 000000000..220d483a8 --- /dev/null +++ b/packages/form-core/tests/FormApi.test-d.ts @@ -0,0 +1,57 @@ +import { assertType, it } from 'vitest' +import { FormApi } from '../src' +import type { ValidationError, ValidationErrorMap } from '../src' + +it('should return all errors matching the right type from getAllErrors', () => { + const form = new FormApi({ + defaultValues: { + firstName: '', + lastName: '', + }, + validators: { + onChange: () => ['onChange'] as const, + onMount: () => 10 as const, + onBlur: () => ({ onBlur: true as const, onBlurNumber: 1 }), + onSubmit: () => 'onSubmit' as const, + onBlurAsync: () => Promise.resolve('onBlurAsync' as const), + onChangeAsync: () => Promise.resolve('onChangeAsync' as const), + onSubmitAsync: () => Promise.resolve('onSubmitAsync' as const), + }, + }) + + const errors = form.getAllErrors() + + errors.form.errorMap.onChange + + assertType<{ + onBlur?: { onBlur: true; onBlurNumber: number } | 'onBlurAsync' + onChange?: readonly ['onChange'] | 'onChangeAsync' + onMount?: 10 + onSubmit?: 'onSubmit' | 'onSubmitAsync' + onServer?: undefined + }>(errors.form.errorMap) + + assertType< + ( + | readonly ['onChange'] + | 'onChangeAsync' + | 10 + | 'onSubmit' + | 'onSubmitAsync' + | { onBlur: true; onBlurNumber: number } + | 'onBlurAsync' + | undefined + )[] + >(errors.form.errors) + + assertType<{ + firstName: { + errors: ValidationError[] + errorMap: ValidationErrorMap + } + lastName: { + errors: ValidationError[] + errorMap: ValidationErrorMap + } + }>(errors.fields) +})