@tanstack/form-core 0.40.4 → 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.
255
- */
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.
242
+ * A record of field metadata for each field in the form, not including the derived properties, like `errors` and such
263
243
  */
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
+ }
437
+
438
+ return fieldMeta
439
+ },
440
+ })
423
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) => {
@@ -661,7 +731,7 @@ export class FormApi<
661
731
 
662
732
  // Validate the fields
663
733
  const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any
664
- this.store.batch(() => {
734
+ batch(() => {
665
735
  fieldsToValidate.forEach((nestedField) => {
666
736
  fieldValidationPromises.push(
667
737
  Promise.resolve().then(() => this.validateField(nestedField, cause)),
@@ -708,7 +778,7 @@ export class FormApi<
708
778
 
709
779
  const fieldsErrorMap: FieldsErrorMapFromValidator<TFormData> = {}
710
780
 
711
- this.store.batch(() => {
781
+ batch(() => {
712
782
  for (const validateObj of validates) {
713
783
  if (!validateObj.validate) continue
714
784
 
@@ -750,7 +820,7 @@ export class FormApi<
750
820
  }
751
821
 
752
822
  if (this.state.errorMap[errorMapKey] !== formError) {
753
- this.store.setState((prev) => ({
823
+ this.baseStore.setState((prev) => ({
754
824
  ...prev,
755
825
  errorMap: {
756
826
  ...prev.errorMap,
@@ -775,7 +845,7 @@ export class FormApi<
775
845
  cause !== 'submit' &&
776
846
  !hasErrored
777
847
  ) {
778
- this.store.setState((prev) => ({
848
+ this.baseStore.setState((prev) => ({
779
849
  ...prev,
780
850
  errorMap: {
781
851
  ...prev.errorMap,
@@ -796,7 +866,7 @@ export class FormApi<
796
866
  const validates = getAsyncValidatorArray(cause, this.options)
797
867
 
798
868
  if (!this.state.isFormValidating) {
799
- this.store.setState((prev) => ({ ...prev, isFormValidating: true }))
869
+ this.baseStore.setState((prev) => ({ ...prev, isFormValidating: true }))
800
870
  }
801
871
 
802
872
  /**
@@ -876,7 +946,7 @@ export class FormApi<
876
946
  }
877
947
  }
878
948
  }
879
- this.store.setState((prev) => ({
949
+ this.baseStore.setState((prev) => ({
880
950
  ...prev,
881
951
  errorMap: {
882
952
  ...prev.errorMap,
@@ -913,7 +983,7 @@ export class FormApi<
913
983
  }
914
984
  }
915
985
 
916
- this.store.setState((prev) => ({
986
+ this.baseStore.setState((prev) => ({
917
987
  ...prev,
918
988
  isFormValidating: false,
919
989
  }))
@@ -944,7 +1014,7 @@ export class FormApi<
944
1014
  * Handles the form submission, performs validation, and calls the appropriate onSubmit or onInvalidSubmit callbacks.
945
1015
  */
946
1016
  handleSubmit = async () => {
947
- this.store.setState((old) => ({
1017
+ this.baseStore.setState((old) => ({
948
1018
  ...old,
949
1019
  // Submission attempts mark the form as not submitted
950
1020
  isSubmitted: false,
@@ -955,10 +1025,10 @@ export class FormApi<
955
1025
  // Don't let invalid forms submit
956
1026
  if (!this.state.canSubmit) return
957
1027
 
958
- this.store.setState((d) => ({ ...d, isSubmitting: true }))
1028
+ this.baseStore.setState((d) => ({ ...d, isSubmitting: true }))
959
1029
 
960
1030
  const done = () => {
961
- this.store.setState((prev) => ({ ...prev, isSubmitting: false }))
1031
+ this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false }))
962
1032
  }
963
1033
 
964
1034
  // Validate form and all fields
@@ -974,7 +1044,7 @@ export class FormApi<
974
1044
  return
975
1045
  }
976
1046
 
977
- this.store.batch(() => {
1047
+ batch(() => {
978
1048
  void (
979
1049
  Object.values(this.fieldInfo) as FieldInfo<TFormData, TFormValidator>[]
980
1050
  ).forEach((field) => {
@@ -989,8 +1059,8 @@ export class FormApi<
989
1059
  // Run the submit code
990
1060
  await this.options.onSubmit?.({ value: this.state.values, formApi: this })
991
1061
 
992
- this.store.batch(() => {
993
- this.store.setState((prev) => ({ ...prev, isSubmitted: true }))
1062
+ batch(() => {
1063
+ this.baseStore.setState((prev) => ({ ...prev, isSubmitted: true }))
994
1064
  done()
995
1065
  })
996
1066
  } catch (err) {
@@ -1041,12 +1111,15 @@ export class FormApi<
1041
1111
  field: TField,
1042
1112
  updater: Updater<FieldMeta>,
1043
1113
  ) => {
1044
- this.store.setState((prev) => {
1114
+ this.baseStore.setState((prev) => {
1045
1115
  return {
1046
1116
  ...prev,
1047
- fieldMeta: {
1048
- ...prev.fieldMeta,
1049
- [field]: functionalUpdate(updater, prev.fieldMeta[field]),
1117
+ fieldMetaBase: {
1118
+ ...prev.fieldMetaBase,
1119
+ [field]: functionalUpdate(
1120
+ updater,
1121
+ prev.fieldMetaBase[field] as never,
1122
+ ),
1050
1123
  },
1051
1124
  }
1052
1125
  })
@@ -1083,7 +1156,7 @@ export class FormApi<
1083
1156
  ) => {
1084
1157
  const dontUpdateMeta = opts?.dontUpdateMeta ?? false
1085
1158
 
1086
- this.store.batch(() => {
1159
+ batch(() => {
1087
1160
  if (!dontUpdateMeta) {
1088
1161
  this.setFieldMeta(field, (prev) => ({
1089
1162
  ...prev,
@@ -1097,7 +1170,7 @@ export class FormApi<
1097
1170
  }))
1098
1171
  }
1099
1172
 
1100
- this.store.setState((prev) => {
1173
+ this.baseStore.setState((prev) => {
1101
1174
  return {
1102
1175
  ...prev,
1103
1176
  values: setBy(prev.values, field, updater),
@@ -1107,10 +1180,10 @@ export class FormApi<
1107
1180
  }
1108
1181
 
1109
1182
  deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {
1110
- this.store.setState((prev) => {
1183
+ this.baseStore.setState((prev) => {
1111
1184
  const newState = { ...prev }
1112
1185
  newState.values = deleteBy(newState.values, field)
1113
- delete newState.fieldMeta[field]
1186
+ delete newState.fieldMetaBase[field]
1114
1187
 
1115
1188
  return newState
1116
1189
  })
@@ -1281,7 +1354,7 @@ export class FormApi<
1281
1354
  * Updates the form's errorMap
1282
1355
  */
1283
1356
  setErrorMap(errorMap: ValidationErrorMap) {
1284
- this.store.setState((prev) => ({
1357
+ this.baseStore.setState((prev) => ({
1285
1358
  ...prev,
1286
1359
  errorMap: {
1287
1360
  ...prev.errorMap,