@tanstack/form-core 0.40.3 → 0.41.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.
- package/dist/cjs/FieldApi.cjs +24 -49
- package/dist/cjs/FieldApi.cjs.map +1 -1
- package/dist/cjs/FieldApi.d.cts +18 -26
- package/dist/cjs/FormApi.cjs +155 -112
- package/dist/cjs/FormApi.cjs.map +1 -1
- package/dist/cjs/FormApi.d.cts +47 -48
- package/dist/esm/FieldApi.d.ts +18 -26
- package/dist/esm/FieldApi.js +25 -50
- package/dist/esm/FieldApi.js.map +1 -1
- package/dist/esm/FormApi.d.ts +47 -48
- package/dist/esm/FormApi.js +156 -113
- package/dist/esm/FormApi.js.map +1 -1
- package/package.json +2 -2
- package/src/FieldApi.ts +41 -82
- package/src/FormApi.ts +241 -181
package/src/FormApi.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Store } from '@tanstack/store'
|
|
1
|
+
import { Derived, Store, batch } from '@tanstack/store'
|
|
2
2
|
import {
|
|
3
3
|
deleteBy,
|
|
4
4
|
functionalUpdate,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
standardSchemaValidator,
|
|
14
14
|
} from './standardSchemaValidator'
|
|
15
15
|
import type { StandardSchemaV1 } from './standardSchemaValidator'
|
|
16
|
-
import type { FieldApi, FieldMeta } from './FieldApi'
|
|
16
|
+
import type { FieldApi, FieldMeta, FieldMetaBase } from './FieldApi'
|
|
17
17
|
import type {
|
|
18
18
|
FormValidationError,
|
|
19
19
|
FormValidationErrorMap,
|
|
@@ -225,23 +225,11 @@ export type FieldInfo<
|
|
|
225
225
|
/**
|
|
226
226
|
* An object representing the current state of the form.
|
|
227
227
|
*/
|
|
228
|
-
export type
|
|
228
|
+
export type BaseFormState<TFormData> = {
|
|
229
229
|
/**
|
|
230
230
|
* The current values of the form fields.
|
|
231
231
|
*/
|
|
232
232
|
values: TFormData
|
|
233
|
-
/**
|
|
234
|
-
* A boolean indicating if the form is currently validating.
|
|
235
|
-
*/
|
|
236
|
-
isFormValidating: boolean
|
|
237
|
-
/**
|
|
238
|
-
* A boolean indicating if the form is valid.
|
|
239
|
-
*/
|
|
240
|
-
isFormValid: boolean
|
|
241
|
-
/**
|
|
242
|
-
* The error array for the form itself.
|
|
243
|
-
*/
|
|
244
|
-
errors: ValidationError[]
|
|
245
233
|
/**
|
|
246
234
|
* The error map for the form itself.
|
|
247
235
|
*/
|
|
@@ -251,17 +239,9 @@ export type FormState<TFormData> = {
|
|
|
251
239
|
*/
|
|
252
240
|
validationMetaMap: Record<ValidationErrorMapKeys, ValidationMeta | undefined>
|
|
253
241
|
/**
|
|
254
|
-
* A record of field metadata for each field in the form
|
|
242
|
+
* A record of field metadata for each field in the form, not including the derived properties, like `errors` and such
|
|
255
243
|
*/
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* A boolean indicating if any of the form fields are currently validating.
|
|
259
|
-
*/
|
|
260
|
-
isFieldsValidating: boolean
|
|
261
|
-
/**
|
|
262
|
-
* A boolean indicating if all the form fields are valid.
|
|
263
|
-
*/
|
|
264
|
-
isFieldsValid: boolean
|
|
244
|
+
fieldMetaBase: Record<DeepKeys<TFormData>, FieldMetaBase>
|
|
265
245
|
/**
|
|
266
246
|
* A boolean indicating if the form is currently in the process of being submitted after `handleSubmit` is called.
|
|
267
247
|
*
|
|
@@ -275,6 +255,41 @@ export type FormState<TFormData> = {
|
|
|
275
255
|
*
|
|
276
256
|
*/
|
|
277
257
|
isSubmitting: boolean
|
|
258
|
+
/**
|
|
259
|
+
* A boolean indicating if the form has been submitted.
|
|
260
|
+
*/
|
|
261
|
+
isSubmitted: boolean
|
|
262
|
+
/**
|
|
263
|
+
* A boolean indicating if the form or any of its fields are currently validating.
|
|
264
|
+
*/
|
|
265
|
+
isValidating: boolean
|
|
266
|
+
/**
|
|
267
|
+
* A counter for tracking the number of submission attempts.
|
|
268
|
+
*/
|
|
269
|
+
submissionAttempts: number
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export type DerivedFormState<TFormData> = {
|
|
273
|
+
/**
|
|
274
|
+
* A boolean indicating if the form is currently validating.
|
|
275
|
+
*/
|
|
276
|
+
isFormValidating: boolean
|
|
277
|
+
/**
|
|
278
|
+
* A boolean indicating if the form is valid.
|
|
279
|
+
*/
|
|
280
|
+
isFormValid: boolean
|
|
281
|
+
/**
|
|
282
|
+
* The error array for the form itself.
|
|
283
|
+
*/
|
|
284
|
+
errors: ValidationError[]
|
|
285
|
+
/**
|
|
286
|
+
* A boolean indicating if any of the form fields are currently validating.
|
|
287
|
+
*/
|
|
288
|
+
isFieldsValidating: boolean
|
|
289
|
+
/**
|
|
290
|
+
* A boolean indicating if all the form fields are valid.
|
|
291
|
+
*/
|
|
292
|
+
isFieldsValid: boolean
|
|
278
293
|
/**
|
|
279
294
|
* A boolean indicating if any of the form fields have been touched.
|
|
280
295
|
*/
|
|
@@ -291,14 +306,6 @@ export type FormState<TFormData> = {
|
|
|
291
306
|
* A boolean indicating if none of the form's fields' values have been modified by the user. `True` if the user have not modified any of the fields. Opposite of `isDirty`.
|
|
292
307
|
*/
|
|
293
308
|
isPristine: boolean
|
|
294
|
-
/**
|
|
295
|
-
* A boolean indicating if the form has been submitted.
|
|
296
|
-
*/
|
|
297
|
-
isSubmitted: boolean
|
|
298
|
-
/**
|
|
299
|
-
* A boolean indicating if the form or any of its fields are currently validating.
|
|
300
|
-
*/
|
|
301
|
-
isValidating: boolean
|
|
302
309
|
/**
|
|
303
310
|
* A boolean indicating if the form and all its fields are valid.
|
|
304
311
|
*/
|
|
@@ -308,31 +315,23 @@ export type FormState<TFormData> = {
|
|
|
308
315
|
*/
|
|
309
316
|
canSubmit: boolean
|
|
310
317
|
/**
|
|
311
|
-
* A
|
|
318
|
+
* A record of field metadata for each field in the form.
|
|
312
319
|
*/
|
|
313
|
-
|
|
320
|
+
fieldMeta: Record<DeepKeys<TFormData>, FieldMeta>
|
|
314
321
|
}
|
|
315
322
|
|
|
323
|
+
export type FormState<TFormData> = BaseFormState<TFormData> &
|
|
324
|
+
DerivedFormState<TFormData>
|
|
325
|
+
|
|
316
326
|
function getDefaultFormState<TFormData>(
|
|
317
327
|
defaultState: Partial<FormState<TFormData>>,
|
|
318
|
-
):
|
|
328
|
+
): BaseFormState<TFormData> {
|
|
319
329
|
return {
|
|
320
330
|
values: defaultState.values ?? ({} as never),
|
|
321
|
-
errors: defaultState.errors ?? [],
|
|
322
331
|
errorMap: defaultState.errorMap ?? {},
|
|
323
|
-
|
|
324
|
-
canSubmit: defaultState.canSubmit ?? true,
|
|
325
|
-
isFieldsValid: defaultState.isFieldsValid ?? false,
|
|
326
|
-
isFieldsValidating: defaultState.isFieldsValidating ?? false,
|
|
327
|
-
isFormValid: defaultState.isFormValid ?? false,
|
|
328
|
-
isFormValidating: defaultState.isFormValidating ?? false,
|
|
332
|
+
fieldMetaBase: defaultState.fieldMetaBase ?? ({} as never),
|
|
329
333
|
isSubmitted: defaultState.isSubmitted ?? false,
|
|
330
334
|
isSubmitting: defaultState.isSubmitting ?? false,
|
|
331
|
-
isTouched: defaultState.isTouched ?? false,
|
|
332
|
-
isBlurred: defaultState.isBlurred ?? false,
|
|
333
|
-
isPristine: defaultState.isPristine ?? true,
|
|
334
|
-
isDirty: defaultState.isDirty ?? false,
|
|
335
|
-
isValid: defaultState.isValid ?? false,
|
|
336
335
|
isValidating: defaultState.isValidating ?? false,
|
|
337
336
|
submissionAttempts: defaultState.submissionAttempts ?? 0,
|
|
338
337
|
validationMetaMap: defaultState.validationMetaMap ?? {
|
|
@@ -366,24 +365,19 @@ export class FormApi<
|
|
|
366
365
|
* The options for the form.
|
|
367
366
|
*/
|
|
368
367
|
options: FormOptions<TFormData, TFormValidator> = {}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
store!: Store<FormState<TFormData>>
|
|
373
|
-
/**
|
|
374
|
-
* The current state of the form.
|
|
375
|
-
*
|
|
376
|
-
* **Note:**
|
|
377
|
-
* Do not use `state` directly, as it is not reactive.
|
|
378
|
-
* Please use useStore(form.store) utility to subscribe to state
|
|
379
|
-
*/
|
|
380
|
-
state!: FormState<TFormData>
|
|
368
|
+
baseStore!: Store<BaseFormState<TFormData>>
|
|
369
|
+
fieldMetaDerived!: Derived<Record<DeepKeys<TFormData>, FieldMeta>>
|
|
370
|
+
store!: Derived<FormState<TFormData>>
|
|
381
371
|
/**
|
|
382
372
|
* A record of field information for each field in the form.
|
|
383
373
|
*/
|
|
384
374
|
fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, TFormValidator>> =
|
|
385
375
|
{} as any
|
|
386
376
|
|
|
377
|
+
get state() {
|
|
378
|
+
return this.store.state
|
|
379
|
+
}
|
|
380
|
+
|
|
387
381
|
/**
|
|
388
382
|
* @private
|
|
389
383
|
*/
|
|
@@ -393,103 +387,172 @@ export class FormApi<
|
|
|
393
387
|
* Constructs a new `FormApi` instance with the given form options.
|
|
394
388
|
*/
|
|
395
389
|
constructor(opts?: FormOptions<TFormData, TFormValidator>) {
|
|
396
|
-
this.
|
|
390
|
+
this.baseStore = new Store(
|
|
397
391
|
getDefaultFormState({
|
|
398
392
|
...(opts?.defaultState as any),
|
|
399
393
|
values: opts?.defaultValues ?? opts?.defaultState?.values,
|
|
400
394
|
isFormValid: true,
|
|
401
395
|
}),
|
|
402
|
-
|
|
403
|
-
onUpdate: () => {
|
|
404
|
-
let { state } = this.store
|
|
405
|
-
// Computed state
|
|
406
|
-
const fieldMetaValues = Object.values(state.fieldMeta) as (
|
|
407
|
-
| FieldMeta
|
|
408
|
-
| undefined
|
|
409
|
-
)[]
|
|
396
|
+
)
|
|
410
397
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
398
|
+
this.fieldMetaDerived = new Derived({
|
|
399
|
+
deps: [this.baseStore],
|
|
400
|
+
fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => {
|
|
401
|
+
const prevVal = _prevVal as
|
|
402
|
+
| Record<DeepKeys<TFormData>, FieldMeta>
|
|
403
|
+
| undefined
|
|
404
|
+
const prevBaseStore = prevDepVals?.[0]
|
|
405
|
+
const currBaseStore = currDepVals[0]
|
|
406
|
+
|
|
407
|
+
const fieldMeta = {} as FormState<TFormData>['fieldMeta']
|
|
408
|
+
for (const fieldName of Object.keys(
|
|
409
|
+
currBaseStore.fieldMetaBase,
|
|
410
|
+
) as Array<keyof typeof currBaseStore.fieldMetaBase>) {
|
|
411
|
+
const currBaseVal = currBaseStore.fieldMetaBase[
|
|
412
|
+
fieldName as never
|
|
413
|
+
] as FieldMetaBase
|
|
414
|
+
|
|
415
|
+
const prevBaseVal = prevBaseStore?.fieldMetaBase[
|
|
416
|
+
fieldName as never
|
|
417
|
+
] as FieldMetaBase | undefined
|
|
418
|
+
|
|
419
|
+
let fieldErrors =
|
|
420
|
+
prevVal?.[fieldName as never as keyof typeof prevVal]?.errors
|
|
421
|
+
if (!prevBaseVal || currBaseVal.errorMap !== prevBaseVal.errorMap) {
|
|
422
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
423
|
+
fieldErrors = Object.values(currBaseVal.errorMap ?? {}).filter(
|
|
424
|
+
(val: unknown) => val !== undefined,
|
|
425
|
+
)
|
|
426
|
+
}
|
|
414
427
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
field?.errorMap &&
|
|
418
|
-
isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),
|
|
419
|
-
)
|
|
428
|
+
// As a primitive, we don't need to aggressively persist the same referencial value for performance reasons
|
|
429
|
+
const isFieldPristine = !currBaseVal.isDirty
|
|
420
430
|
|
|
421
|
-
|
|
422
|
-
|
|
431
|
+
fieldMeta[fieldName] = {
|
|
432
|
+
...currBaseVal,
|
|
433
|
+
errors: fieldErrors,
|
|
434
|
+
isPristine: isFieldPristine,
|
|
435
|
+
} as FieldMeta
|
|
436
|
+
}
|
|
423
437
|
|
|
438
|
+
return fieldMeta
|
|
439
|
+
},
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
this.store = new Derived({
|
|
443
|
+
deps: [this.baseStore, this.fieldMetaDerived],
|
|
444
|
+
fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => {
|
|
445
|
+
const prevVal = _prevVal as FormState<TFormData> | undefined
|
|
446
|
+
const prevBaseStore = prevDepVals?.[0]
|
|
447
|
+
const currBaseStore = currDepVals[0]
|
|
448
|
+
|
|
449
|
+
// Computed state
|
|
450
|
+
const fieldMetaValues = Object.values(currBaseStore.fieldMetaBase) as (
|
|
451
|
+
| FieldMeta
|
|
452
|
+
| undefined
|
|
453
|
+
)[]
|
|
454
|
+
|
|
455
|
+
const isFieldsValidating = fieldMetaValues.some(
|
|
456
|
+
(field) => field?.isValidating,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
const isFieldsValid = !fieldMetaValues.some(
|
|
460
|
+
(field) =>
|
|
461
|
+
field?.errorMap &&
|
|
462
|
+
isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
const isTouched = fieldMetaValues.some((field) => field?.isTouched)
|
|
466
|
+
const isBlurred = fieldMetaValues.some((field) => field?.isBlurred)
|
|
467
|
+
|
|
468
|
+
const shouldInvalidateOnMount =
|
|
424
469
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
425
|
-
|
|
426
|
-
state.errorMap.onMount = undefined
|
|
427
|
-
}
|
|
470
|
+
isTouched && currBaseStore?.errorMap?.onMount
|
|
428
471
|
|
|
429
|
-
|
|
430
|
-
|
|
472
|
+
const isDirty = fieldMetaValues.some((field) => field?.isDirty)
|
|
473
|
+
const isPristine = !isDirty
|
|
431
474
|
|
|
432
|
-
|
|
475
|
+
const hasOnMountError = Boolean(
|
|
476
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
477
|
+
currBaseStore.errorMap?.onMount ||
|
|
433
478
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
fieldMetaValues.some((f) => f?.errorMap?.onMount),
|
|
437
|
-
)
|
|
479
|
+
fieldMetaValues.some((f) => f?.errorMap?.onMount),
|
|
480
|
+
)
|
|
438
481
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
482
|
+
const isValidating = !!isFieldsValidating
|
|
483
|
+
|
|
484
|
+
// As `errors` is not a primitive, we need to aggressively persist the same referencial value for performance reasons
|
|
485
|
+
let errors = prevVal?.errors ?? []
|
|
486
|
+
if (
|
|
487
|
+
!prevBaseStore ||
|
|
488
|
+
currBaseStore.errorMap !== prevBaseStore.errorMap
|
|
489
|
+
) {
|
|
490
|
+
errors = Object.values(currBaseStore.errorMap).reduce(
|
|
491
|
+
(prev, curr) => {
|
|
492
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
493
|
+
if (curr === undefined) return prev
|
|
494
|
+
if (typeof curr === 'string') {
|
|
495
|
+
prev.push(curr)
|
|
496
|
+
return prev
|
|
497
|
+
} else if (curr && isFormValidationError(curr)) {
|
|
498
|
+
prev.push(curr.form)
|
|
499
|
+
return prev
|
|
500
|
+
}
|
|
448
501
|
return prev
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const isValid = isFieldsValid && isFormValid
|
|
454
|
-
const canSubmit =
|
|
455
|
-
(state.submissionAttempts === 0 &&
|
|
456
|
-
!isTouched &&
|
|
457
|
-
!hasOnMountError) ||
|
|
458
|
-
(!isValidating && !state.isSubmitting && isValid)
|
|
459
|
-
|
|
460
|
-
state = {
|
|
461
|
-
...state,
|
|
462
|
-
isFieldsValidating,
|
|
463
|
-
isFieldsValid,
|
|
464
|
-
isFormValid,
|
|
465
|
-
isValid,
|
|
466
|
-
canSubmit,
|
|
467
|
-
isTouched,
|
|
468
|
-
isBlurred,
|
|
469
|
-
isPristine,
|
|
470
|
-
isDirty,
|
|
471
|
-
}
|
|
502
|
+
},
|
|
503
|
+
[] as ValidationError[],
|
|
504
|
+
)
|
|
505
|
+
}
|
|
472
506
|
|
|
473
|
-
|
|
474
|
-
|
|
507
|
+
const isFormValid = errors.length === 0
|
|
508
|
+
const isValid = isFieldsValid && isFormValid
|
|
509
|
+
const canSubmit =
|
|
510
|
+
(currBaseStore.submissionAttempts === 0 &&
|
|
511
|
+
!isTouched &&
|
|
512
|
+
!hasOnMountError) ||
|
|
513
|
+
(!isValidating && !currBaseStore.isSubmitting && isValid)
|
|
514
|
+
|
|
515
|
+
let errorMap = currBaseStore.errorMap
|
|
516
|
+
if (shouldInvalidateOnMount) {
|
|
517
|
+
errors = errors.filter(
|
|
518
|
+
(err) => err !== currBaseStore.errorMap.onMount,
|
|
519
|
+
)
|
|
520
|
+
errorMap = Object.assign(errorMap, { onMount: undefined })
|
|
521
|
+
}
|
|
475
522
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
523
|
+
let state = {
|
|
524
|
+
...currBaseStore,
|
|
525
|
+
errorMap,
|
|
526
|
+
fieldMeta: this.fieldMetaDerived.state,
|
|
527
|
+
errors,
|
|
528
|
+
isFieldsValidating,
|
|
529
|
+
isFieldsValid,
|
|
530
|
+
isFormValid,
|
|
531
|
+
isValid,
|
|
532
|
+
canSubmit,
|
|
533
|
+
isTouched,
|
|
534
|
+
isBlurred,
|
|
535
|
+
isPristine,
|
|
536
|
+
isDirty,
|
|
537
|
+
} as FormState<TFormData>
|
|
538
|
+
|
|
539
|
+
// Only run transform if state has shallowly changed - IE how React.useEffect works
|
|
540
|
+
const transformArray = this.options.transform?.deps ?? []
|
|
541
|
+
const shouldTransform =
|
|
542
|
+
transformArray.length !== this.prevTransformArray.length ||
|
|
543
|
+
transformArray.some((val, i) => val !== this.prevTransformArray[i])
|
|
544
|
+
|
|
545
|
+
if (shouldTransform) {
|
|
546
|
+
const newObj = Object.assign({}, this, { state })
|
|
547
|
+
// This mutates the state
|
|
548
|
+
this.options.transform?.fn(newObj)
|
|
549
|
+
state = newObj.state
|
|
550
|
+
this.prevTransformArray = transformArray
|
|
551
|
+
}
|
|
481
552
|
|
|
482
|
-
|
|
483
|
-
// This mutates the state
|
|
484
|
-
this.options.transform?.fn(this)
|
|
485
|
-
this.store.state = this.state
|
|
486
|
-
this.prevTransformArray = transformArray
|
|
487
|
-
}
|
|
488
|
-
},
|
|
553
|
+
return state
|
|
489
554
|
},
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
this.state = this.store.state
|
|
555
|
+
})
|
|
493
556
|
|
|
494
557
|
this.update(opts || {})
|
|
495
558
|
}
|
|
@@ -530,10 +593,17 @@ export class FormApi<
|
|
|
530
593
|
}
|
|
531
594
|
|
|
532
595
|
mount = () => {
|
|
596
|
+
const cleanupFieldMetaDerived = this.fieldMetaDerived.mount()
|
|
597
|
+
const cleanupStoreDerived = this.store.mount()
|
|
598
|
+
const cleanup = () => {
|
|
599
|
+
cleanupFieldMetaDerived()
|
|
600
|
+
cleanupStoreDerived()
|
|
601
|
+
}
|
|
533
602
|
const { onMount } = this.options.validators || {}
|
|
534
|
-
if (!onMount) return
|
|
535
|
-
|
|
603
|
+
if (!onMount) return cleanup
|
|
536
604
|
this.validateSync('mount')
|
|
605
|
+
|
|
606
|
+
return cleanup
|
|
537
607
|
}
|
|
538
608
|
|
|
539
609
|
/**
|
|
@@ -547,7 +617,7 @@ export class FormApi<
|
|
|
547
617
|
// Options need to be updated first so that when the store is updated, the state is correct for the derived state
|
|
548
618
|
this.options = options
|
|
549
619
|
|
|
550
|
-
|
|
620
|
+
batch(() => {
|
|
551
621
|
const shouldUpdateValues =
|
|
552
622
|
options.defaultValues &&
|
|
553
623
|
options.defaultValues !== oldOptions.defaultValues &&
|
|
@@ -557,7 +627,7 @@ export class FormApi<
|
|
|
557
627
|
options.defaultState !== oldOptions.defaultState &&
|
|
558
628
|
!this.state.isTouched
|
|
559
629
|
|
|
560
|
-
this.
|
|
630
|
+
this.baseStore.setState(() =>
|
|
561
631
|
getDefaultFormState(
|
|
562
632
|
Object.assign(
|
|
563
633
|
{},
|
|
@@ -585,7 +655,7 @@ export class FormApi<
|
|
|
585
655
|
*/
|
|
586
656
|
reset = (values?: TFormData, opts?: { keepDefaultValues?: boolean }) => {
|
|
587
657
|
const { fieldMeta: currentFieldMeta } = this.state
|
|
588
|
-
const
|
|
658
|
+
const fieldMetaBase = this.resetFieldMeta(currentFieldMeta)
|
|
589
659
|
|
|
590
660
|
if (values && !opts?.keepDefaultValues) {
|
|
591
661
|
this.options = {
|
|
@@ -594,14 +664,14 @@ export class FormApi<
|
|
|
594
664
|
}
|
|
595
665
|
}
|
|
596
666
|
|
|
597
|
-
this.
|
|
667
|
+
this.baseStore.setState(() =>
|
|
598
668
|
getDefaultFormState({
|
|
599
669
|
...(this.options.defaultState as any),
|
|
600
670
|
values:
|
|
601
671
|
values ??
|
|
602
672
|
this.options.defaultValues ??
|
|
603
673
|
this.options.defaultState?.values,
|
|
604
|
-
|
|
674
|
+
fieldMetaBase,
|
|
605
675
|
}),
|
|
606
676
|
)
|
|
607
677
|
}
|
|
@@ -611,7 +681,7 @@ export class FormApi<
|
|
|
611
681
|
*/
|
|
612
682
|
validateAllFields = async (cause: ValidationCause) => {
|
|
613
683
|
const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
|
|
614
|
-
|
|
684
|
+
batch(() => {
|
|
615
685
|
void (
|
|
616
686
|
Object.values(this.fieldInfo) as FieldInfo<any, TFormValidator>[]
|
|
617
687
|
).forEach((field) => {
|
|
@@ -627,12 +697,6 @@ export class FormApi<
|
|
|
627
697
|
// Mark them as touched
|
|
628
698
|
field.instance.setMeta((prev) => ({ ...prev, isTouched: true }))
|
|
629
699
|
}
|
|
630
|
-
|
|
631
|
-
// If any fields are not blurred
|
|
632
|
-
if (!field.instance.state.meta.isBlurred) {
|
|
633
|
-
// Mark them as blurred
|
|
634
|
-
field.instance.setMeta((prev) => ({ ...prev, isBlurred: true }))
|
|
635
|
-
}
|
|
636
700
|
})
|
|
637
701
|
})
|
|
638
702
|
|
|
@@ -667,7 +731,7 @@ export class FormApi<
|
|
|
667
731
|
|
|
668
732
|
// Validate the fields
|
|
669
733
|
const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
|
|
670
|
-
|
|
734
|
+
batch(() => {
|
|
671
735
|
fieldsToValidate.forEach((nestedField) => {
|
|
672
736
|
fieldValidationPromises.push(
|
|
673
737
|
Promise.resolve().then(() => this.validateField(nestedField, cause)),
|
|
@@ -696,12 +760,6 @@ export class FormApi<
|
|
|
696
760
|
fieldInstance.setMeta((prev) => ({ ...prev, isTouched: true }))
|
|
697
761
|
}
|
|
698
762
|
|
|
699
|
-
// If the field is not blurred (same logic as in validateAllFields)
|
|
700
|
-
if (!fieldInstance.state.meta.isBlurred) {
|
|
701
|
-
// Mark it as blurred
|
|
702
|
-
fieldInstance.setMeta((prev) => ({ ...prev, isBlurred: true }))
|
|
703
|
-
}
|
|
704
|
-
|
|
705
763
|
return fieldInstance.validate(cause)
|
|
706
764
|
}
|
|
707
765
|
|
|
@@ -720,7 +778,7 @@ export class FormApi<
|
|
|
720
778
|
|
|
721
779
|
const fieldsErrorMap: FieldsErrorMapFromValidator<TFormData> = {}
|
|
722
780
|
|
|
723
|
-
|
|
781
|
+
batch(() => {
|
|
724
782
|
for (const validateObj of validates) {
|
|
725
783
|
if (!validateObj.validate) continue
|
|
726
784
|
|
|
@@ -762,7 +820,7 @@ export class FormApi<
|
|
|
762
820
|
}
|
|
763
821
|
|
|
764
822
|
if (this.state.errorMap[errorMapKey] !== formError) {
|
|
765
|
-
this.
|
|
823
|
+
this.baseStore.setState((prev) => ({
|
|
766
824
|
...prev,
|
|
767
825
|
errorMap: {
|
|
768
826
|
...prev.errorMap,
|
|
@@ -787,7 +845,7 @@ export class FormApi<
|
|
|
787
845
|
cause !== 'submit' &&
|
|
788
846
|
!hasErrored
|
|
789
847
|
) {
|
|
790
|
-
this.
|
|
848
|
+
this.baseStore.setState((prev) => ({
|
|
791
849
|
...prev,
|
|
792
850
|
errorMap: {
|
|
793
851
|
...prev.errorMap,
|
|
@@ -808,7 +866,7 @@ export class FormApi<
|
|
|
808
866
|
const validates = getAsyncValidatorArray(cause, this.options)
|
|
809
867
|
|
|
810
868
|
if (!this.state.isFormValidating) {
|
|
811
|
-
this.
|
|
869
|
+
this.baseStore.setState((prev) => ({ ...prev, isFormValidating: true }))
|
|
812
870
|
}
|
|
813
871
|
|
|
814
872
|
/**
|
|
@@ -888,7 +946,7 @@ export class FormApi<
|
|
|
888
946
|
}
|
|
889
947
|
}
|
|
890
948
|
}
|
|
891
|
-
this.
|
|
949
|
+
this.baseStore.setState((prev) => ({
|
|
892
950
|
...prev,
|
|
893
951
|
errorMap: {
|
|
894
952
|
...prev.errorMap,
|
|
@@ -925,7 +983,7 @@ export class FormApi<
|
|
|
925
983
|
}
|
|
926
984
|
}
|
|
927
985
|
|
|
928
|
-
this.
|
|
986
|
+
this.baseStore.setState((prev) => ({
|
|
929
987
|
...prev,
|
|
930
988
|
isFormValidating: false,
|
|
931
989
|
}))
|
|
@@ -956,7 +1014,7 @@ export class FormApi<
|
|
|
956
1014
|
* Handles the form submission, performs validation, and calls the appropriate onSubmit or onInvalidSubmit callbacks.
|
|
957
1015
|
*/
|
|
958
1016
|
handleSubmit = async () => {
|
|
959
|
-
this.
|
|
1017
|
+
this.baseStore.setState((old) => ({
|
|
960
1018
|
...old,
|
|
961
1019
|
// Submission attempts mark the form as not submitted
|
|
962
1020
|
isSubmitted: false,
|
|
@@ -967,10 +1025,10 @@ export class FormApi<
|
|
|
967
1025
|
// Don't let invalid forms submit
|
|
968
1026
|
if (!this.state.canSubmit) return
|
|
969
1027
|
|
|
970
|
-
this.
|
|
1028
|
+
this.baseStore.setState((d) => ({ ...d, isSubmitting: true }))
|
|
971
1029
|
|
|
972
1030
|
const done = () => {
|
|
973
|
-
this.
|
|
1031
|
+
this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false }))
|
|
974
1032
|
}
|
|
975
1033
|
|
|
976
1034
|
// Validate form and all fields
|
|
@@ -986,7 +1044,7 @@ export class FormApi<
|
|
|
986
1044
|
return
|
|
987
1045
|
}
|
|
988
1046
|
|
|
989
|
-
|
|
1047
|
+
batch(() => {
|
|
990
1048
|
void (
|
|
991
1049
|
Object.values(this.fieldInfo) as FieldInfo<TFormData, TFormValidator>[]
|
|
992
1050
|
).forEach((field) => {
|
|
@@ -1001,8 +1059,8 @@ export class FormApi<
|
|
|
1001
1059
|
// Run the submit code
|
|
1002
1060
|
await this.options.onSubmit?.({ value: this.state.values, formApi: this })
|
|
1003
1061
|
|
|
1004
|
-
|
|
1005
|
-
this.
|
|
1062
|
+
batch(() => {
|
|
1063
|
+
this.baseStore.setState((prev) => ({ ...prev, isSubmitted: true }))
|
|
1006
1064
|
done()
|
|
1007
1065
|
})
|
|
1008
1066
|
} catch (err) {
|
|
@@ -1053,12 +1111,15 @@ export class FormApi<
|
|
|
1053
1111
|
field: TField,
|
|
1054
1112
|
updater: Updater<FieldMeta>,
|
|
1055
1113
|
) => {
|
|
1056
|
-
this.
|
|
1114
|
+
this.baseStore.setState((prev) => {
|
|
1057
1115
|
return {
|
|
1058
1116
|
...prev,
|
|
1059
|
-
|
|
1060
|
-
...prev.
|
|
1061
|
-
[field]: functionalUpdate(
|
|
1117
|
+
fieldMetaBase: {
|
|
1118
|
+
...prev.fieldMetaBase,
|
|
1119
|
+
[field]: functionalUpdate(
|
|
1120
|
+
updater,
|
|
1121
|
+
prev.fieldMetaBase[field] as never,
|
|
1122
|
+
),
|
|
1062
1123
|
},
|
|
1063
1124
|
}
|
|
1064
1125
|
})
|
|
@@ -1095,12 +1156,11 @@ export class FormApi<
|
|
|
1095
1156
|
) => {
|
|
1096
1157
|
const dontUpdateMeta = opts?.dontUpdateMeta ?? false
|
|
1097
1158
|
|
|
1098
|
-
|
|
1159
|
+
batch(() => {
|
|
1099
1160
|
if (!dontUpdateMeta) {
|
|
1100
1161
|
this.setFieldMeta(field, (prev) => ({
|
|
1101
1162
|
...prev,
|
|
1102
1163
|
isTouched: true,
|
|
1103
|
-
isBlurred: true,
|
|
1104
1164
|
isDirty: true,
|
|
1105
1165
|
errorMap: {
|
|
1106
1166
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
@@ -1110,7 +1170,7 @@ export class FormApi<
|
|
|
1110
1170
|
}))
|
|
1111
1171
|
}
|
|
1112
1172
|
|
|
1113
|
-
this.
|
|
1173
|
+
this.baseStore.setState((prev) => {
|
|
1114
1174
|
return {
|
|
1115
1175
|
...prev,
|
|
1116
1176
|
values: setBy(prev.values, field, updater),
|
|
@@ -1120,10 +1180,10 @@ export class FormApi<
|
|
|
1120
1180
|
}
|
|
1121
1181
|
|
|
1122
1182
|
deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {
|
|
1123
|
-
this.
|
|
1183
|
+
this.baseStore.setState((prev) => {
|
|
1124
1184
|
const newState = { ...prev }
|
|
1125
1185
|
newState.values = deleteBy(newState.values, field)
|
|
1126
|
-
delete newState.
|
|
1186
|
+
delete newState.fieldMetaBase[field]
|
|
1127
1187
|
|
|
1128
1188
|
return newState
|
|
1129
1189
|
})
|
|
@@ -1294,7 +1354,7 @@ export class FormApi<
|
|
|
1294
1354
|
* Updates the form's errorMap
|
|
1295
1355
|
*/
|
|
1296
1356
|
setErrorMap(errorMap: ValidationErrorMap) {
|
|
1297
|
-
this.
|
|
1357
|
+
this.baseStore.setState((prev) => ({
|
|
1298
1358
|
...prev,
|
|
1299
1359
|
errorMap: {
|
|
1300
1360
|
...prev.errorMap,
|