@tanstack/form-core 0.10.3 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/cjs/FieldApi.d.ts +95 -0
  2. package/dist/cjs/FormApi.d.ts +118 -0
  3. package/dist/cjs/index.cjs +926 -0
  4. package/dist/cjs/index.cjs.map +1 -0
  5. package/dist/cjs/index.d.cts +5 -0
  6. package/dist/cjs/index.d.ts +5 -0
  7. package/dist/cjs/index.js +926 -0
  8. package/dist/cjs/mergeForm.d.ts +4 -0
  9. package/dist/cjs/tests/FieldApi.spec.d.ts +1 -0
  10. package/dist/cjs/tests/FieldApi.test-d.d.ts +1 -0
  11. package/dist/cjs/tests/FormApi.spec.d.ts +1 -0
  12. package/dist/cjs/tests/mutateMergeDeep.spec.d.ts +1 -0
  13. package/dist/cjs/tests/utils.d.ts +1 -0
  14. package/dist/cjs/tests/utils.spec.d.ts +1 -0
  15. package/dist/cjs/types.d.ts +14 -0
  16. package/dist/cjs/utils.d.ts +57 -0
  17. package/dist/mjs/FieldApi.d.ts +95 -0
  18. package/dist/mjs/FormApi.d.ts +118 -0
  19. package/dist/mjs/index.d.mts +5 -0
  20. package/dist/mjs/index.d.ts +5 -0
  21. package/dist/mjs/index.js +926 -0
  22. package/dist/mjs/index.mjs +926 -0
  23. package/dist/mjs/index.mjs.map +1 -0
  24. package/dist/mjs/mergeForm.d.ts +4 -0
  25. package/dist/mjs/tests/FieldApi.spec.d.ts +1 -0
  26. package/dist/mjs/tests/FieldApi.test-d.d.ts +1 -0
  27. package/dist/mjs/tests/FormApi.spec.d.ts +1 -0
  28. package/dist/mjs/tests/mutateMergeDeep.spec.d.ts +1 -0
  29. package/dist/mjs/tests/utils.d.ts +1 -0
  30. package/dist/mjs/tests/utils.spec.d.ts +1 -0
  31. package/dist/mjs/types.d.ts +14 -0
  32. package/dist/mjs/utils.d.ts +57 -0
  33. package/package.json +16 -21
  34. package/src/FieldApi.ts +328 -236
  35. package/src/FormApi.ts +302 -216
  36. package/src/index.ts +1 -0
  37. package/src/mergeForm.ts +42 -0
  38. package/src/tests/FieldApi.spec.ts +135 -48
  39. package/src/tests/FieldApi.test-d.ts +10 -6
  40. package/src/tests/FormApi.spec.ts +171 -62
  41. package/src/tests/mutateMergeDeep.spec.ts +32 -0
  42. package/src/tests/utils.ts +1 -1
  43. package/src/types.ts +11 -2
  44. package/src/utils.ts +137 -14
  45. package/build/legacy/FieldApi.cjs +0 -340
  46. package/build/legacy/FieldApi.cjs.map +0 -1
  47. package/build/legacy/FieldApi.d.cts +0 -4
  48. package/build/legacy/FieldApi.d.ts +0 -4
  49. package/build/legacy/FieldApi.js +0 -315
  50. package/build/legacy/FieldApi.js.map +0 -1
  51. package/build/legacy/FormApi.cjs +0 -438
  52. package/build/legacy/FormApi.cjs.map +0 -1
  53. package/build/legacy/FormApi.d.cts +0 -4
  54. package/build/legacy/FormApi.d.ts +0 -4
  55. package/build/legacy/FormApi.js +0 -419
  56. package/build/legacy/FormApi.js.map +0 -1
  57. package/build/legacy/index.cjs +0 -31
  58. package/build/legacy/index.cjs.map +0 -1
  59. package/build/legacy/index.d.cts +0 -170
  60. package/build/legacy/index.d.ts +0 -170
  61. package/build/legacy/index.js +0 -6
  62. package/build/legacy/index.js.map +0 -1
  63. package/build/legacy/types.cjs +0 -19
  64. package/build/legacy/types.cjs.map +0 -1
  65. package/build/legacy/types.d.cts +0 -7
  66. package/build/legacy/types.d.ts +0 -7
  67. package/build/legacy/types.js +0 -1
  68. package/build/legacy/types.js.map +0 -1
  69. package/build/legacy/utils.cjs +0 -132
  70. package/build/legacy/utils.cjs.map +0 -1
  71. package/build/legacy/utils.d.cts +0 -37
  72. package/build/legacy/utils.d.ts +0 -37
  73. package/build/legacy/utils.js +0 -103
  74. package/build/legacy/utils.js.map +0 -1
  75. package/build/modern/FieldApi.cjs +0 -337
  76. package/build/modern/FieldApi.cjs.map +0 -1
  77. package/build/modern/FieldApi.d.cts +0 -4
  78. package/build/modern/FieldApi.d.ts +0 -4
  79. package/build/modern/FieldApi.js +0 -312
  80. package/build/modern/FieldApi.js.map +0 -1
  81. package/build/modern/FormApi.cjs +0 -431
  82. package/build/modern/FormApi.cjs.map +0 -1
  83. package/build/modern/FormApi.d.cts +0 -4
  84. package/build/modern/FormApi.d.ts +0 -4
  85. package/build/modern/FormApi.js +0 -412
  86. package/build/modern/FormApi.js.map +0 -1
  87. package/build/modern/index.cjs +0 -31
  88. package/build/modern/index.cjs.map +0 -1
  89. package/build/modern/index.d.cts +0 -170
  90. package/build/modern/index.d.ts +0 -170
  91. package/build/modern/index.js +0 -6
  92. package/build/modern/index.js.map +0 -1
  93. package/build/modern/types.cjs +0 -19
  94. package/build/modern/types.cjs.map +0 -1
  95. package/build/modern/types.d.cts +0 -7
  96. package/build/modern/types.d.ts +0 -7
  97. package/build/modern/types.js +0 -1
  98. package/build/modern/types.js.map +0 -1
  99. package/build/modern/utils.cjs +0 -132
  100. package/build/modern/utils.cjs.map +0 -1
  101. package/build/modern/utils.d.cts +0 -37
  102. package/build/modern/utils.d.ts +0 -37
  103. package/build/modern/utils.js +0 -103
  104. package/build/modern/utils.js.map +0 -1
package/src/FieldApi.ts CHANGED
@@ -1,115 +1,209 @@
1
1
  import { Store } from '@tanstack/store'
2
- import type { FormApi, ValidationErrorMap } from './FormApi'
3
- import type { ValidationError, Validator } from './types'
2
+ import type { FormApi } from './FormApi'
3
+ import type {
4
+ ValidationCause,
5
+ ValidationError,
6
+ ValidationErrorMap,
7
+ Validator,
8
+ } from './types'
4
9
  import type { DeepKeys, DeepValue, Updater } from './utils'
10
+ import { getAsyncValidatorArray, getSyncValidatorArray } from './utils'
5
11
 
6
- export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount'
7
-
8
- type ValidateFn<
12
+ export type FieldValidateFn<
9
13
  TParentData,
10
14
  TName extends DeepKeys<TParentData>,
11
- ValidatorType,
15
+ TFieldValidator extends
16
+ | Validator<DeepValue<TParentData, TName>, unknown>
17
+ | undefined = undefined,
18
+ TFormValidator extends
19
+ | Validator<TParentData, unknown>
20
+ | undefined = undefined,
12
21
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
13
- > = (
14
- value: TData,
15
- fieldApi: FieldApi<TParentData, TName, ValidatorType, TData>,
16
- ) => ValidationError
22
+ > = (props: {
23
+ value: TData
24
+ fieldApi: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>
25
+ }) => ValidationError
17
26
 
18
- type ValidateOrFn<
27
+ export type FieldValidateOrFn<
19
28
  TParentData,
20
29
  TName extends DeepKeys<TParentData>,
21
- ValidatorType,
22
- FormValidator,
30
+ TFieldValidator extends
31
+ | Validator<DeepValue<TParentData, TName>, unknown>
32
+ | undefined = undefined,
33
+ TFormValidator extends
34
+ | Validator<TParentData, unknown>
35
+ | undefined = undefined,
23
36
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
24
- > = ValidatorType extends Validator<TData>
37
+ > = TFieldValidator extends Validator<TData, infer TFN>
25
38
  ?
26
- | Parameters<ReturnType<ValidatorType>['validate']>[1]
27
- | ValidateFn<TParentData, TName, ValidatorType, TData>
28
- : FormValidator extends Validator<TData>
29
- ?
30
- | Parameters<ReturnType<FormValidator>['validate']>[1]
31
- | ValidateFn<TParentData, TName, ValidatorType, TData>
32
- : ValidateFn<TParentData, TName, ValidatorType, TData>
33
-
34
- type ValidateAsyncFn<
39
+ | TFN
40
+ | FieldValidateFn<
41
+ TParentData,
42
+ TName,
43
+ TFieldValidator,
44
+ TFormValidator,
45
+ TData
46
+ >
47
+ : TFormValidator extends Validator<TParentData, infer FFN>
48
+ ?
49
+ | FFN
50
+ | FieldValidateFn<
51
+ TParentData,
52
+ TName,
53
+ TFieldValidator,
54
+ TFormValidator,
55
+ TData
56
+ >
57
+ : FieldValidateFn<
58
+ TParentData,
59
+ TName,
60
+ TFieldValidator,
61
+ TFormValidator,
62
+ TData
63
+ >
64
+
65
+ export type FieldValidateAsyncFn<
35
66
  TParentData,
36
67
  TName extends DeepKeys<TParentData>,
37
- ValidatorType,
68
+ TFieldValidator extends
69
+ | Validator<DeepValue<TParentData, TName>, unknown>
70
+ | undefined = undefined,
71
+ TFormValidator extends
72
+ | Validator<TParentData, unknown>
73
+ | undefined = undefined,
38
74
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
39
- > = (
40
- value: TData,
41
- fieldApi: FieldApi<TParentData, TName, ValidatorType, TData>,
42
- ) => ValidationError | Promise<ValidationError>
75
+ > = (options: {
76
+ value: TData
77
+ fieldApi: FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>
78
+ signal: AbortSignal
79
+ }) => ValidationError | Promise<ValidationError>
43
80
 
44
- type AsyncValidateOrFn<
81
+ export type FieldAsyncValidateOrFn<
45
82
  TParentData,
46
83
  TName extends DeepKeys<TParentData>,
47
- ValidatorType,
48
- FormValidator,
84
+ TFieldValidator extends
85
+ | Validator<DeepValue<TParentData, TName>, unknown>
86
+ | undefined = undefined,
87
+ TFormValidator extends
88
+ | Validator<TParentData, unknown>
89
+ | undefined = undefined,
49
90
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
50
- > = ValidatorType extends Validator<TData>
51
- ?
52
- | Parameters<ReturnType<ValidatorType>['validate']>[1]
53
- | ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
54
- : FormValidator extends Validator<TData>
91
+ > = TFieldValidator extends Validator<TData, infer TFN>
55
92
  ?
56
- | Parameters<ReturnType<FormValidator>['validate']>[1]
57
- | ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
58
- : ValidateAsyncFn<TParentData, TName, ValidatorType, TData>
59
-
60
- export interface FieldOptions<
93
+ | TFN
94
+ | FieldValidateAsyncFn<
95
+ TParentData,
96
+ TName,
97
+ TFieldValidator,
98
+ TFormValidator,
99
+ TData
100
+ >
101
+ : TFormValidator extends Validator<TParentData, infer FFN>
102
+ ?
103
+ | FFN
104
+ | FieldValidateAsyncFn<
105
+ TParentData,
106
+ TName,
107
+ TFieldValidator,
108
+ TFormValidator,
109
+ TData
110
+ >
111
+ : FieldValidateAsyncFn<
112
+ TParentData,
113
+ TName,
114
+ TFieldValidator,
115
+ TFormValidator,
116
+ TData
117
+ >
118
+
119
+ export interface FieldValidators<
61
120
  TParentData,
62
121
  TName extends DeepKeys<TParentData>,
63
- ValidatorType,
64
- FormValidator,
122
+ TFieldValidator extends
123
+ | Validator<DeepValue<TParentData, TName>, unknown>
124
+ | undefined = undefined,
125
+ TFormValidator extends
126
+ | Validator<TParentData, unknown>
127
+ | undefined = undefined,
65
128
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
66
129
  > {
67
- name: TName
68
- index?: TData extends any[] ? number : never
69
- defaultValue?: TData
70
- asyncDebounceMs?: number
71
- asyncAlways?: boolean
72
- preserveValue?: boolean
73
- validator?: ValidatorType
74
- onMount?: (
75
- formApi: FieldApi<TParentData, TName, ValidatorType, TData>,
76
- ) => void
77
- onChange?: ValidateOrFn<
130
+ onMount?: FieldValidateOrFn<
78
131
  TParentData,
79
132
  TName,
80
- ValidatorType,
81
- FormValidator,
133
+ TFieldValidator,
134
+ TFormValidator,
82
135
  TData
83
136
  >
84
- onChangeAsync?: AsyncValidateOrFn<
137
+ onChange?: FieldValidateOrFn<
85
138
  TParentData,
86
139
  TName,
87
- ValidatorType,
88
- FormValidator,
140
+ TFieldValidator,
141
+ TFormValidator,
142
+ TData
143
+ >
144
+ onChangeAsync?: FieldAsyncValidateOrFn<
145
+ TParentData,
146
+ TName,
147
+ TFieldValidator,
148
+ TFormValidator,
89
149
  TData
90
150
  >
91
151
  onChangeAsyncDebounceMs?: number
92
- onBlur?: ValidateOrFn<TParentData, TName, ValidatorType, FormValidator, TData>
93
- onBlurAsync?: AsyncValidateOrFn<
152
+ onBlur?: FieldValidateOrFn<
94
153
  TParentData,
95
154
  TName,
96
- ValidatorType,
97
- FormValidator,
155
+ TFieldValidator,
156
+ TFormValidator,
157
+ TData
158
+ >
159
+ onBlurAsync?: FieldAsyncValidateOrFn<
160
+ TParentData,
161
+ TName,
162
+ TFieldValidator,
163
+ TFormValidator,
98
164
  TData
99
165
  >
100
166
  onBlurAsyncDebounceMs?: number
101
- onSubmit?: ValidateOrFn<
167
+ onSubmit?: FieldValidateOrFn<
168
+ TParentData,
169
+ TName,
170
+ TFieldValidator,
171
+ TFormValidator,
172
+ TData
173
+ >
174
+ onSubmitAsync?: FieldAsyncValidateOrFn<
102
175
  TParentData,
103
176
  TName,
104
- ValidatorType,
105
- FormValidator,
177
+ TFieldValidator,
178
+ TFormValidator,
106
179
  TData
107
180
  >
108
- onSubmitAsync?: AsyncValidateOrFn<
181
+ onSubmitAsyncDebounceMs?: number
182
+ }
183
+
184
+ export interface FieldOptions<
185
+ TParentData,
186
+ TName extends DeepKeys<TParentData>,
187
+ TFieldValidator extends
188
+ | Validator<DeepValue<TParentData, TName>, unknown>
189
+ | undefined = undefined,
190
+ TFormValidator extends
191
+ | Validator<TParentData, unknown>
192
+ | undefined = undefined,
193
+ TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
194
+ > {
195
+ name: TName
196
+ index?: TData extends any[] ? number : never
197
+ defaultValue?: TData
198
+ asyncDebounceMs?: number
199
+ asyncAlways?: boolean
200
+ preserveValue?: boolean
201
+ validatorAdapter?: TFieldValidator
202
+ validators?: FieldValidators<
109
203
  TParentData,
110
204
  TName,
111
- ValidatorType,
112
- FormValidator,
205
+ TFieldValidator,
206
+ TFormValidator,
113
207
  TData
114
208
  >
115
209
  defaultMeta?: Partial<FieldMeta>
@@ -118,17 +212,21 @@ export interface FieldOptions<
118
212
  export interface FieldApiOptions<
119
213
  TParentData,
120
214
  TName extends DeepKeys<TParentData>,
121
- ValidatorType,
122
- FormValidator,
215
+ TFieldValidator extends
216
+ | Validator<DeepValue<TParentData, TName>, unknown>
217
+ | undefined = undefined,
218
+ TFormValidator extends
219
+ | Validator<TParentData, unknown>
220
+ | undefined = undefined,
123
221
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
124
222
  > extends FieldOptions<
125
223
  TParentData,
126
224
  TName,
127
- ValidatorType,
128
- FormValidator,
225
+ TFieldValidator,
226
+ TFormValidator,
129
227
  TData
130
228
  > {
131
- form: FormApi<TParentData, FormValidator>
229
+ form: FormApi<TParentData, TFormValidator>
132
230
  }
133
231
 
134
232
  export type FieldMeta = {
@@ -153,14 +251,30 @@ export type ResolveName<TParentData> = unknown extends TParentData
153
251
  export class FieldApi<
154
252
  TParentData,
155
253
  TName extends DeepKeys<TParentData>,
156
- ValidatorType,
157
- FormValidator,
254
+ TFieldValidator extends
255
+ | Validator<DeepValue<TParentData, TName>, unknown>
256
+ | undefined = undefined,
257
+ TFormValidator extends
258
+ | Validator<TParentData, unknown>
259
+ | undefined = undefined,
158
260
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
159
261
  > {
160
262
  uid: number
161
- form: FieldApiOptions<TParentData, TName, ValidatorType, TData>['form']
263
+ form: FieldApiOptions<
264
+ TParentData,
265
+ TName,
266
+ TFieldValidator,
267
+ TFormValidator,
268
+ TData
269
+ >['form']
162
270
  name!: DeepKeys<TParentData>
163
- options: FieldApiOptions<TParentData, TName, ValidatorType, TData> = {} as any
271
+ options: FieldApiOptions<
272
+ TParentData,
273
+ TName,
274
+ TFieldValidator,
275
+ TFormValidator,
276
+ TData
277
+ > = {} as any
164
278
  store!: Store<FieldState<TData>>
165
279
  state!: FieldState<TData>
166
280
  prevState!: FieldState<TData>
@@ -169,8 +283,8 @@ export class FieldApi<
169
283
  opts: FieldApiOptions<
170
284
  TParentData,
171
285
  TName,
172
- ValidatorType,
173
- FormValidator,
286
+ TFieldValidator,
287
+ TFormValidator,
174
288
  TData
175
289
  >,
176
290
  ) {
@@ -224,6 +338,29 @@ export class FieldApi<
224
338
  this.options = opts as never
225
339
  }
226
340
 
341
+ runValidator<
342
+ TValue extends { value: TData; fieldApi: FieldApi<any, any, any, any> },
343
+ TType extends 'validate' | 'validateAsync',
344
+ >(props: {
345
+ validate: TType extends 'validate'
346
+ ? FieldValidateOrFn<any, any, any, any>
347
+ : FieldAsyncValidateOrFn<any, any, any, any>
348
+ value: TValue
349
+ type: TType
350
+ }): ReturnType<ReturnType<Validator<any>>[TType]> {
351
+ const adapters = [
352
+ this.form.options.validatorAdapter,
353
+ this.options.validatorAdapter,
354
+ ] as const
355
+ for (const adapter of adapters) {
356
+ if (adapter && typeof props.validate !== 'function') {
357
+ return adapter()[props.type](props.value, props.validate) as never
358
+ }
359
+ }
360
+
361
+ return (props.validate as FieldValidateFn<any, any>)(props.value) as never
362
+ }
363
+
227
364
  mount = () => {
228
365
  const info = this.getInfo()
229
366
  info.instances[this.uid] = this as never
@@ -243,7 +380,25 @@ export class FieldApi<
243
380
  })
244
381
 
245
382
  this.update(this.options as never)
246
- this.options.onMount?.(this as never)
383
+ const { onMount } = this.options.validators || {}
384
+
385
+ if (onMount) {
386
+ const error = this.runValidator({
387
+ validate: onMount,
388
+ value: {
389
+ value: this.state.value,
390
+ fieldApi: this,
391
+ },
392
+ type: 'validate',
393
+ })
394
+ if (error) {
395
+ this.setMeta((prev) => ({
396
+ ...prev,
397
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
398
+ errorMap: { ...prev?.errorMap, onMount: error },
399
+ }))
400
+ }
401
+ }
247
402
 
248
403
  return () => {
249
404
  const preserveValue = this.options.preserveValue
@@ -260,7 +415,13 @@ export class FieldApi<
260
415
  }
261
416
 
262
417
  update = (
263
- opts: FieldApiOptions<TParentData, TName, ValidatorType, TData>,
418
+ opts: FieldApiOptions<
419
+ TParentData,
420
+ TName,
421
+ TFieldValidator,
422
+ TFormValidator,
423
+ TData
424
+ >,
264
425
  ) => {
265
426
  // Default Value
266
427
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -330,52 +491,20 @@ export class FieldApi<
330
491
  TSubData extends DeepValue<TData, TSubName> = DeepValue<TData, TSubName>,
331
492
  >(
332
493
  name: TSubName,
333
- ): FieldApi<TData, TSubName, ValidatorType, TSubData> =>
494
+ ): FieldApi<
495
+ TData,
496
+ TSubName,
497
+ Validator<TSubData, unknown> | undefined,
498
+ Validator<TData, unknown> | undefined,
499
+ TSubData
500
+ > =>
334
501
  new FieldApi({
335
502
  name: `${this.name}.${name}` as never,
336
503
  form: this.form,
337
504
  }) as any
338
505
 
339
506
  validateSync = (value = this.state.value, cause: ValidationCause) => {
340
- const { onChange, onBlur, onSubmit } = this.options
341
-
342
- const validates =
343
- // https://github.com/TanStack/form/issues/490
344
- cause === 'submit'
345
- ? ([
346
- { cause: 'change', validate: onChange },
347
- { cause: 'blur', validate: onBlur },
348
- { cause: 'submit', validate: onSubmit },
349
- ] as const)
350
- : cause === 'change'
351
- ? ([{ cause: 'change', validate: onChange }] as const)
352
- : ([{ cause: 'blur', validate: onBlur }] as const)
353
-
354
- // Use the validationCount for all field instances to
355
- // track freshness of the validation
356
- const validationCount = (this.getInfo().validationCount || 0) + 1
357
- this.getInfo().validationCount = validationCount
358
-
359
- const doValidate = (validate: (typeof validates)[number]['validate']) => {
360
- if (this.options.validator && typeof validate !== 'function') {
361
- return (this.options.validator as Validator<TData>)().validate(
362
- value,
363
- validate,
364
- )
365
- }
366
-
367
- if (this.form.options.validator && typeof validate !== 'function') {
368
- return (this.form.options.validator as Validator<TData>)().validate(
369
- value,
370
- validate,
371
- )
372
- }
373
-
374
- return (validate as ValidateFn<TParentData, TName, ValidatorType, TData>)(
375
- value,
376
- this as never,
377
- )
378
- }
507
+ const validates = getSyncValidatorArray(cause, this.options)
379
508
 
380
509
  // Needs type cast as eslint errantly believes this is always falsy
381
510
  let hasErrored = false as boolean
@@ -383,7 +512,13 @@ export class FieldApi<
383
512
  this.form.store.batch(() => {
384
513
  for (const validateObj of validates) {
385
514
  if (!validateObj.validate) continue
386
- const error = normalizeError(doValidate(validateObj.validate))
515
+ const error = normalizeError(
516
+ this.runValidator({
517
+ validate: validateObj.validate,
518
+ value: { value, fieldApi: this },
519
+ type: 'validate',
520
+ }),
521
+ )
387
522
  const errorMapKey = getErrorMapKey(validateObj.cause)
388
523
  if (this.state.meta.errorMap[errorMapKey] !== error) {
389
524
  this.setMeta((prev) => ({
@@ -419,130 +554,86 @@ export class FieldApi<
419
554
  }))
420
555
  }
421
556
 
422
- // If a sync error is encountered for the errorMapKey (eg. onChange), cancel any async validation
423
- if (hasErrored) {
424
- this.cancelValidateAsync()
425
- }
426
-
427
557
  return { hasErrored }
428
558
  }
429
559
 
430
- __leaseValidateAsync = () => {
431
- const count = (this.getInfo().validationAsyncCount || 0) + 1
432
- this.getInfo().validationAsyncCount = count
433
- return count
434
- }
435
-
436
- cancelValidateAsync = () => {
437
- // Lease a new validation count to ignore any pending validations
438
- this.__leaseValidateAsync()
439
- // Cancel any pending validation state
440
- this.setMeta((prev) => ({
441
- ...prev,
442
- isValidating: false,
443
- }))
444
- }
445
-
446
560
  validateAsync = async (value = this.state.value, cause: ValidationCause) => {
447
- const {
448
- onChangeAsync,
449
- onBlurAsync,
450
- onSubmitAsync,
451
- asyncDebounceMs,
452
- onBlurAsyncDebounceMs,
453
- onChangeAsyncDebounceMs,
454
- } = this.options
455
-
456
- const validate =
457
- cause === 'change'
458
- ? onChangeAsync
459
- : cause === 'submit'
460
- ? onSubmitAsync
461
- : onBlurAsync
462
- if (!validate) return []
463
- const debounceMs =
464
- cause === 'submit'
465
- ? 0
466
- : (cause === 'change'
467
- ? onChangeAsyncDebounceMs
468
- : onBlurAsyncDebounceMs) ??
469
- asyncDebounceMs ??
470
- 0
471
-
472
- if (this.state.meta.isValidating !== true) {
561
+ const validates = getAsyncValidatorArray(cause, this.options)
562
+
563
+ if (!this.state.meta.isValidating) {
473
564
  this.setMeta((prev) => ({ ...prev, isValidating: true }))
474
565
  }
475
566
 
476
- // Use the validationCount for all field instances to
477
- // track freshness of the validation
478
- const validationAsyncCount = this.__leaseValidateAsync()
479
-
480
- const checkLatest = () =>
481
- validationAsyncCount === this.getInfo().validationAsyncCount
482
-
483
- if (!this.getInfo().validationPromise) {
484
- this.getInfo().validationPromise = new Promise((resolve, reject) => {
485
- this.getInfo().validationResolve = resolve
486
- this.getInfo().validationReject = reject
487
- })
488
- }
567
+ /**
568
+ * We have to use a for loop and generate our promises this way, otherwise it won't be sync
569
+ * when there are no validators needed to be run
570
+ */
571
+ const promises: Promise<ValidationError | undefined>[] = []
489
572
 
490
- if (debounceMs > 0) {
491
- await new Promise((r) => setTimeout(r, debounceMs))
492
- }
573
+ for (const validateObj of validates) {
574
+ if (!validateObj.validate) continue
575
+ const key = getErrorMapKey(validateObj.cause)
576
+ const fieldValidatorMeta = this.getInfo().validationMetaMap[key]
493
577
 
494
- const doValidate = () => {
495
- if (this.options.validator && typeof validate !== 'function') {
496
- return (this.options.validator as Validator<TData>)().validateAsync(
497
- value,
498
- validate,
499
- )
500
- }
578
+ fieldValidatorMeta?.lastAbortController.abort()
579
+ const controller = new AbortController()
501
580
 
502
- if (this.form.options.validator && typeof validate !== 'function') {
503
- return (
504
- this.form.options.validator as Validator<TData>
505
- )().validateAsync(value, validate)
581
+ this.getInfo().validationMetaMap[key] = {
582
+ lastAbortController: controller,
506
583
  }
507
584
 
508
- return (validate as ValidateFn<TParentData, TName, ValidatorType, TData>)(
509
- value,
510
- this as never,
585
+ promises.push(
586
+ new Promise<ValidationError | undefined>(async (resolve) => {
587
+ let rawError!: ValidationError | undefined
588
+ try {
589
+ rawError = await new Promise((rawResolve, rawReject) => {
590
+ setTimeout(async () => {
591
+ if (controller.signal.aborted) return rawResolve(undefined)
592
+ try {
593
+ rawResolve(
594
+ await this.runValidator({
595
+ validate: validateObj.validate,
596
+ value: {
597
+ value,
598
+ fieldApi: this,
599
+ signal: controller.signal,
600
+ },
601
+ type: 'validateAsync',
602
+ }),
603
+ )
604
+ } catch (e) {
605
+ rawReject(e)
606
+ }
607
+ }, validateObj.debounceMs)
608
+ })
609
+ } catch (e: unknown) {
610
+ rawError = e as ValidationError
611
+ }
612
+ const error = normalizeError(rawError)
613
+ this.setMeta((prev) => {
614
+ return {
615
+ ...prev,
616
+ errorMap: {
617
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
618
+ ...prev?.errorMap,
619
+ [getErrorMapKey(cause)]: error,
620
+ },
621
+ }
622
+ })
623
+
624
+ resolve(error)
625
+ }),
511
626
  )
512
627
  }
513
628
 
514
- // Only kick off validation if this validation is the latest attempt
515
- if (checkLatest()) {
516
- const prevErrors = this.getMeta().errors
517
- try {
518
- const rawError = await doValidate()
519
- if (checkLatest()) {
520
- const error = normalizeError(rawError)
521
- this.setMeta((prev) => ({
522
- ...prev,
523
- isValidating: false,
524
- errorMap: {
525
- ...prev.errorMap,
526
- [getErrorMapKey(cause)]: error,
527
- },
528
- }))
529
- this.getInfo().validationResolve?.([...prevErrors, error])
530
- }
531
- } catch (error) {
532
- if (checkLatest()) {
533
- this.getInfo().validationReject?.([...prevErrors, error])
534
- throw error
535
- }
536
- } finally {
537
- if (checkLatest()) {
538
- this.setMeta((prev) => ({ ...prev, isValidating: false }))
539
- delete this.getInfo().validationPromise
540
- }
541
- }
629
+ let results: ValidationError[] = []
630
+ if (promises.length) {
631
+ results = await Promise.all(promises)
542
632
  }
543
633
 
544
- // Always return the latest validation promise to the caller
545
- return (await this.getInfo().validationPromise) ?? []
634
+ this.setMeta((prev) => ({ ...prev, isValidating: false }))
635
+
636
+ return results.filter(Boolean)
546
637
  }
547
638
 
548
639
  validate = (
@@ -559,10 +650,8 @@ export class FieldApi<
559
650
  // Attempt to sync validate first
560
651
  const { hasErrored } = this.validateSync(value, cause)
561
652
 
562
- if (hasErrored) {
563
- if (!this.options.asyncAlways) {
564
- return this.state.meta.errors
565
- }
653
+ if (hasErrored && !this.options.asyncAlways) {
654
+ return this.state.meta.errors
566
655
  }
567
656
  // No error? Attempt async validation
568
657
  return this.validateAsync(value, cause)
@@ -598,11 +687,14 @@ function getErrorMapKey(cause: ValidationCause) {
598
687
  switch (cause) {
599
688
  case 'submit':
600
689
  return 'onSubmit'
601
- case 'change':
602
- return 'onChange'
603
690
  case 'blur':
604
691
  return 'onBlur'
605
692
  case 'mount':
606
693
  return 'onMount'
694
+ case 'server':
695
+ return 'onServer'
696
+ case 'change':
697
+ default:
698
+ return 'onChange'
607
699
  }
608
700
  }