@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/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 FormState<TFormData> = {
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
- fieldMeta: Record<DeepKeys<TFormData>, FieldMeta>
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 counter for tracking the number of submission attempts.
318
+ * A record of field metadata for each field in the form.
312
319
  */
313
- submissionAttempts: number
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
- ): FormState<TFormData> {
328
+ ): BaseFormState<TFormData> {
319
329
  return {
320
330
  values: defaultState.values ?? ({} as never),
321
- errors: defaultState.errors ?? [],
322
331
  errorMap: defaultState.errorMap ?? {},
323
- fieldMeta: defaultState.fieldMeta ?? ({} as never),
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
- * A [TanStack Store instance](https://tanstack.com/store/latest/docs/reference/Store) that keeps track of the form's state.
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.store = new Store<FormState<TFormData>>(
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
- const isFieldsValidating = fieldMetaValues.some(
412
- (field) => field?.isValidating,
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
- const isFieldsValid = !fieldMetaValues.some(
416
- (field) =>
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
- const isTouched = fieldMetaValues.some((field) => field?.isTouched)
422
- const isBlurred = fieldMetaValues.some((field) => field?.isBlurred)
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
- if (isTouched && state?.errorMap?.onMount) {
426
- state.errorMap.onMount = undefined
427
- }
470
+ isTouched && currBaseStore?.errorMap?.onMount
428
471
 
429
- const isDirty = fieldMetaValues.some((field) => field?.isDirty)
430
- const isPristine = !isDirty
472
+ const isDirty = fieldMetaValues.some((field) => field?.isDirty)
473
+ const isPristine = !isDirty
431
474
 
432
- const hasOnMountError = Boolean(
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
- state.errorMap?.onMount ||
435
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
436
- fieldMetaValues.some((f) => f?.errorMap?.onMount),
437
- )
479
+ fieldMetaValues.some((f) => f?.errorMap?.onMount),
480
+ )
438
481
 
439
- const isValidating = isFieldsValidating || state.isFormValidating
440
- state.errors = Object.values(state.errorMap).reduce((prev, curr) => {
441
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
442
- if (curr === undefined) return prev
443
- if (typeof curr === 'string') {
444
- prev.push(curr)
445
- return prev
446
- } else if (curr && isFormValidationError(curr)) {
447
- prev.push(curr.form)
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
- return prev
451
- }, [] as ValidationError[])
452
- const isFormValid = state.errors.length === 0
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
- this.state = state
474
- this.store.state = this.state
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
- // Only run transform if state has shallowly changed - IE how React.useEffect works
477
- const transformArray = this.options.transform?.deps ?? []
478
- const shouldTransform =
479
- transformArray.length !== this.prevTransformArray.length ||
480
- transformArray.some((val, i) => val !== this.prevTransformArray[i])
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
- if (shouldTransform) {
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
- this.store.batch(() => {
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.store.setState(() =>
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 fieldMeta = this.resetFieldMeta(currentFieldMeta)
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.store.setState(() =>
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
- fieldMeta,
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
- this.store.batch(() => {
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
- this.store.batch(() => {
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
- this.store.batch(() => {
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.store.setState((prev) => ({
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.store.setState((prev) => ({
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.store.setState((prev) => ({ ...prev, isFormValidating: true }))
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.store.setState((prev) => ({
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.store.setState((prev) => ({
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.store.setState((old) => ({
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.store.setState((d) => ({ ...d, isSubmitting: true }))
1028
+ this.baseStore.setState((d) => ({ ...d, isSubmitting: true }))
971
1029
 
972
1030
  const done = () => {
973
- this.store.setState((prev) => ({ ...prev, isSubmitting: false }))
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
- this.store.batch(() => {
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
- this.store.batch(() => {
1005
- this.store.setState((prev) => ({ ...prev, isSubmitted: true }))
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.store.setState((prev) => {
1114
+ this.baseStore.setState((prev) => {
1057
1115
  return {
1058
1116
  ...prev,
1059
- fieldMeta: {
1060
- ...prev.fieldMeta,
1061
- [field]: functionalUpdate(updater, prev.fieldMeta[field]),
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
- this.store.batch(() => {
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.store.setState((prev) => {
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.store.setState((prev) => {
1183
+ this.baseStore.setState((prev) => {
1124
1184
  const newState = { ...prev }
1125
1185
  newState.values = deleteBy(newState.values, field)
1126
- delete newState.fieldMeta[field]
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.store.setState((prev) => ({
1357
+ this.baseStore.setState((prev) => ({
1298
1358
  ...prev,
1299
1359
  errorMap: {
1300
1360
  ...prev.errorMap,