@tanstack/form-core 1.26.0 → 1.27.1

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.
@@ -308,6 +308,10 @@ export type AnyFormState = FormState<any, any, any, any, any, any, any, any, any
308
308
  * A type representing the Form API with all generics set to `any` for convenience.
309
309
  */
310
310
  export type AnyFormApi = FormApi<any, any, any, any, any, any, any, any, any, any, any, any>;
311
+ /**
312
+ * We cannot use methods and must use arrow functions. Otherwise, our React adapters
313
+ * will break due to loss of the method when using spread.
314
+ */
311
315
  /**
312
316
  * A class representing the Form API. It handles the logic and interactions with the form state.
313
317
  *
@@ -343,7 +347,7 @@ export declare class FormApi<in out TFormData, in out TOnMount extends undefined
343
347
  /**
344
348
  * @private
345
349
  */
346
- private _formId;
350
+ _formId: string;
347
351
  /**
348
352
  * @private
349
353
  */
@@ -406,11 +410,12 @@ export declare class FormApi<in out TFormData, in out TOnMount extends undefined
406
410
  * @private
407
411
  */
408
412
  validate: (cause: ValidationCause) => FormErrorMapFromValidator<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnDynamic, TOnDynamicAsync> | Promise<FormErrorMapFromValidator<TFormData, TOnMount, TOnChange, TOnChangeAsync, TOnBlur, TOnBlurAsync, TOnSubmit, TOnSubmitAsync, TOnDynamic, TOnDynamicAsync>>;
413
+ handleSubmit(): Promise<void>;
414
+ handleSubmit(submitMeta: TSubmitMeta): Promise<void>;
409
415
  /**
410
416
  * Handles the form submission, performs validation, and calls the appropriate onSubmit or onSubmitInvalid callbacks.
411
417
  */
412
- handleSubmit(): Promise<void>;
413
- handleSubmit(submitMeta: TSubmitMeta): Promise<void>;
418
+ _handleSubmit: (submitMeta?: TSubmitMeta) => Promise<void>;
414
419
  /**
415
420
  * Gets the value of the specified field.
416
421
  */
@@ -468,7 +473,7 @@ export declare class FormApi<in out TFormData, in out TOnMount extends undefined
468
473
  /**
469
474
  * Updates the form's errorMap
470
475
  */
471
- setErrorMap(errorMap: FormValidationErrorMap<TFormData, UnwrapFormValidateOrFn<TOnMount>, UnwrapFormValidateOrFn<TOnChange>, UnwrapFormAsyncValidateOrFn<TOnChangeAsync>, UnwrapFormValidateOrFn<TOnBlur>, UnwrapFormAsyncValidateOrFn<TOnBlurAsync>, UnwrapFormValidateOrFn<TOnSubmit>, UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>, UnwrapFormValidateOrFn<TOnDynamic>, UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>, UnwrapFormAsyncValidateOrFn<TOnServer>>): void;
476
+ setErrorMap: (errorMap: FormValidationErrorMap<TFormData, UnwrapFormValidateOrFn<TOnMount>, UnwrapFormValidateOrFn<TOnChange>, UnwrapFormAsyncValidateOrFn<TOnChangeAsync>, UnwrapFormValidateOrFn<TOnBlur>, UnwrapFormAsyncValidateOrFn<TOnBlurAsync>, UnwrapFormValidateOrFn<TOnSubmit>, UnwrapFormAsyncValidateOrFn<TOnSubmitAsync>, UnwrapFormValidateOrFn<TOnDynamic>, UnwrapFormAsyncValidateOrFn<TOnDynamicAsync>, UnwrapFormAsyncValidateOrFn<TOnServer>>) => void;
472
477
  /**
473
478
  * Returns form and field level errors
474
479
  */
@@ -1,6 +1,6 @@
1
1
  import { batch, Store, Derived } from "@tanstack/store";
2
2
  import { throttle } from "@tanstack/pacer";
3
- import { evaluate, getSyncValidatorArray, determineFormLevelErrorSourceAndValue, getAsyncValidatorArray, getBy, functionalUpdate, setBy, deleteBy, mergeOpts, uuid, isNonEmptyArray, isGlobalFormValidationError } from "./utils.js";
3
+ import { evaluate, getSyncValidatorArray, determineFormLevelErrorSourceAndValue, getAsyncValidatorArray, getBy, functionalUpdate, setBy, deleteBy, mergeOpts, isGlobalFormValidationError, uuid, isNonEmptyArray } from "./utils.js";
4
4
  import { defaultValidationLogic } from "./ValidationLogic.js";
5
5
  import { standardSchemaValidators, isStandardSchemaValidator } from "./standardSchemaValidator.js";
6
6
  import { defaultFieldMeta, metaHelper } from "./metaHelper.js";
@@ -403,6 +403,121 @@ class FormApi {
403
403
  }
404
404
  return this.validateAsync(cause);
405
405
  };
406
+ this._handleSubmit = async (submitMeta) => {
407
+ this.baseStore.setState((old) => ({
408
+ ...old,
409
+ // Submission attempts mark the form as not submitted
410
+ isSubmitted: false,
411
+ // Count submission attempts
412
+ submissionAttempts: old.submissionAttempts + 1,
413
+ isSubmitSuccessful: false
414
+ // Reset isSubmitSuccessful at the start of submission
415
+ }));
416
+ batch(() => {
417
+ void Object.values(this.fieldInfo).forEach(
418
+ (field) => {
419
+ if (!field.instance) return;
420
+ if (!field.instance.state.meta.isTouched) {
421
+ field.instance.setMeta((prev) => ({ ...prev, isTouched: true }));
422
+ }
423
+ }
424
+ );
425
+ });
426
+ const submitMetaArg = submitMeta ?? this.options.onSubmitMeta;
427
+ if (!this.state.canSubmit && !this._devtoolsSubmissionOverride) {
428
+ this.options.onSubmitInvalid?.({
429
+ value: this.state.values,
430
+ formApi: this,
431
+ meta: submitMetaArg
432
+ });
433
+ return;
434
+ }
435
+ this.baseStore.setState((d) => ({ ...d, isSubmitting: true }));
436
+ const done = () => {
437
+ this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false }));
438
+ };
439
+ await this.validateAllFields("submit");
440
+ if (!this.state.isFieldsValid) {
441
+ done();
442
+ this.options.onSubmitInvalid?.({
443
+ value: this.state.values,
444
+ formApi: this,
445
+ meta: submitMetaArg
446
+ });
447
+ formEventClient.emit("form-submission", {
448
+ id: this._formId,
449
+ submissionAttempt: this.state.submissionAttempts,
450
+ successful: false,
451
+ stage: "validateAllFields",
452
+ errors: Object.values(this.state.fieldMeta).map((meta) => meta.errors).flat()
453
+ });
454
+ return;
455
+ }
456
+ await this.validate("submit");
457
+ if (!this.state.isValid) {
458
+ done();
459
+ this.options.onSubmitInvalid?.({
460
+ value: this.state.values,
461
+ formApi: this,
462
+ meta: submitMetaArg
463
+ });
464
+ formEventClient.emit("form-submission", {
465
+ id: this._formId,
466
+ submissionAttempt: this.state.submissionAttempts,
467
+ successful: false,
468
+ stage: "validate",
469
+ errors: this.state.errors
470
+ });
471
+ return;
472
+ }
473
+ batch(() => {
474
+ void Object.values(this.fieldInfo).forEach(
475
+ (field) => {
476
+ field.instance?.options.listeners?.onSubmit?.({
477
+ value: field.instance.state.value,
478
+ fieldApi: field.instance
479
+ });
480
+ }
481
+ );
482
+ });
483
+ this.options.listeners?.onSubmit?.({ formApi: this, meta: submitMetaArg });
484
+ try {
485
+ await this.options.onSubmit?.({
486
+ value: this.state.values,
487
+ formApi: this,
488
+ meta: submitMetaArg
489
+ });
490
+ batch(() => {
491
+ this.baseStore.setState((prev) => ({
492
+ ...prev,
493
+ isSubmitted: true,
494
+ isSubmitSuccessful: true
495
+ // Set isSubmitSuccessful to true on successful submission
496
+ }));
497
+ formEventClient.emit("form-submission", {
498
+ id: this._formId,
499
+ submissionAttempt: this.state.submissionAttempts,
500
+ successful: true
501
+ });
502
+ done();
503
+ });
504
+ } catch (err) {
505
+ this.baseStore.setState((prev) => ({
506
+ ...prev,
507
+ isSubmitSuccessful: false
508
+ // Ensure isSubmitSuccessful is false if an error occurs
509
+ }));
510
+ formEventClient.emit("form-submission", {
511
+ id: this._formId,
512
+ submissionAttempt: this.state.submissionAttempts,
513
+ successful: false,
514
+ stage: "inflight",
515
+ onError: err
516
+ });
517
+ done();
518
+ throw err;
519
+ }
520
+ };
406
521
  this.getFieldValue = (field) => getBy(this.state.values, field);
407
522
  this.getFieldMeta = (field) => {
408
523
  return this.state.fieldMeta[field];
@@ -625,6 +740,48 @@ class FormApi {
625
740
  };
626
741
  });
627
742
  };
743
+ this.setErrorMap = (errorMap) => {
744
+ batch(() => {
745
+ Object.entries(errorMap).forEach(([key, value]) => {
746
+ const errorMapKey = key;
747
+ if (isGlobalFormValidationError(value)) {
748
+ const { formError, fieldErrors } = normalizeError(value);
749
+ for (const fieldName of Object.keys(
750
+ this.fieldInfo
751
+ )) {
752
+ const fieldMeta = this.getFieldMeta(fieldName);
753
+ if (!fieldMeta) continue;
754
+ this.setFieldMeta(fieldName, (prev) => ({
755
+ ...prev,
756
+ errorMap: {
757
+ ...prev.errorMap,
758
+ [errorMapKey]: fieldErrors?.[fieldName]
759
+ },
760
+ errorSourceMap: {
761
+ ...prev.errorSourceMap,
762
+ [errorMapKey]: "form"
763
+ }
764
+ }));
765
+ }
766
+ this.baseStore.setState((prev) => ({
767
+ ...prev,
768
+ errorMap: {
769
+ ...prev.errorMap,
770
+ [errorMapKey]: formError
771
+ }
772
+ }));
773
+ } else {
774
+ this.baseStore.setState((prev) => ({
775
+ ...prev,
776
+ errorMap: {
777
+ ...prev.errorMap,
778
+ [errorMapKey]: value
779
+ }
780
+ }));
781
+ }
782
+ });
783
+ });
784
+ };
628
785
  this.getAllErrors = () => {
629
786
  return {
630
787
  form: {
@@ -863,165 +1020,8 @@ class FormApi {
863
1020
  }
864
1021
  return props.validate(props.value);
865
1022
  }
866
- async handleSubmit(submitMeta) {
867
- this.baseStore.setState((old) => ({
868
- ...old,
869
- // Submission attempts mark the form as not submitted
870
- isSubmitted: false,
871
- // Count submission attempts
872
- submissionAttempts: old.submissionAttempts + 1,
873
- isSubmitSuccessful: false
874
- // Reset isSubmitSuccessful at the start of submission
875
- }));
876
- batch(() => {
877
- void Object.values(this.fieldInfo).forEach(
878
- (field) => {
879
- if (!field.instance) return;
880
- if (!field.instance.state.meta.isTouched) {
881
- field.instance.setMeta((prev) => ({ ...prev, isTouched: true }));
882
- }
883
- }
884
- );
885
- });
886
- const submitMetaArg = submitMeta ?? this.options.onSubmitMeta;
887
- if (!this.state.canSubmit && !this._devtoolsSubmissionOverride) {
888
- this.options.onSubmitInvalid?.({
889
- value: this.state.values,
890
- formApi: this,
891
- meta: submitMetaArg
892
- });
893
- return;
894
- }
895
- this.baseStore.setState((d) => ({ ...d, isSubmitting: true }));
896
- const done = () => {
897
- this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false }));
898
- };
899
- await this.validateAllFields("submit");
900
- if (!this.state.isFieldsValid) {
901
- done();
902
- this.options.onSubmitInvalid?.({
903
- value: this.state.values,
904
- formApi: this,
905
- meta: submitMetaArg
906
- });
907
- formEventClient.emit("form-submission", {
908
- id: this._formId,
909
- submissionAttempt: this.state.submissionAttempts,
910
- successful: false,
911
- stage: "validateAllFields",
912
- errors: Object.values(this.state.fieldMeta).map((meta) => meta.errors).flat()
913
- });
914
- return;
915
- }
916
- await this.validate("submit");
917
- if (!this.state.isValid) {
918
- done();
919
- this.options.onSubmitInvalid?.({
920
- value: this.state.values,
921
- formApi: this,
922
- meta: submitMetaArg
923
- });
924
- formEventClient.emit("form-submission", {
925
- id: this._formId,
926
- submissionAttempt: this.state.submissionAttempts,
927
- successful: false,
928
- stage: "validate",
929
- errors: this.state.errors
930
- });
931
- return;
932
- }
933
- batch(() => {
934
- void Object.values(this.fieldInfo).forEach(
935
- (field) => {
936
- field.instance?.options.listeners?.onSubmit?.({
937
- value: field.instance.state.value,
938
- fieldApi: field.instance
939
- });
940
- }
941
- );
942
- });
943
- this.options.listeners?.onSubmit?.({ formApi: this, meta: submitMetaArg });
944
- try {
945
- await this.options.onSubmit?.({
946
- value: this.state.values,
947
- formApi: this,
948
- meta: submitMetaArg
949
- });
950
- batch(() => {
951
- this.baseStore.setState((prev) => ({
952
- ...prev,
953
- isSubmitted: true,
954
- isSubmitSuccessful: true
955
- // Set isSubmitSuccessful to true on successful submission
956
- }));
957
- formEventClient.emit("form-submission", {
958
- id: this._formId,
959
- submissionAttempt: this.state.submissionAttempts,
960
- successful: true
961
- });
962
- done();
963
- });
964
- } catch (err) {
965
- this.baseStore.setState((prev) => ({
966
- ...prev,
967
- isSubmitSuccessful: false
968
- // Ensure isSubmitSuccessful is false if an error occurs
969
- }));
970
- formEventClient.emit("form-submission", {
971
- id: this._formId,
972
- submissionAttempt: this.state.submissionAttempts,
973
- successful: false,
974
- stage: "inflight",
975
- onError: err
976
- });
977
- done();
978
- throw err;
979
- }
980
- }
981
- /**
982
- * Updates the form's errorMap
983
- */
984
- setErrorMap(errorMap) {
985
- batch(() => {
986
- Object.entries(errorMap).forEach(([key, value]) => {
987
- const errorMapKey = key;
988
- if (isGlobalFormValidationError(value)) {
989
- const { formError, fieldErrors } = normalizeError(value);
990
- for (const fieldName of Object.keys(
991
- this.fieldInfo
992
- )) {
993
- const fieldMeta = this.getFieldMeta(fieldName);
994
- if (!fieldMeta) continue;
995
- this.setFieldMeta(fieldName, (prev) => ({
996
- ...prev,
997
- errorMap: {
998
- ...prev.errorMap,
999
- [errorMapKey]: fieldErrors?.[fieldName]
1000
- },
1001
- errorSourceMap: {
1002
- ...prev.errorSourceMap,
1003
- [errorMapKey]: "form"
1004
- }
1005
- }));
1006
- }
1007
- this.baseStore.setState((prev) => ({
1008
- ...prev,
1009
- errorMap: {
1010
- ...prev.errorMap,
1011
- [errorMapKey]: formError
1012
- }
1013
- }));
1014
- } else {
1015
- this.baseStore.setState((prev) => ({
1016
- ...prev,
1017
- errorMap: {
1018
- ...prev.errorMap,
1019
- [errorMapKey]: value
1020
- }
1021
- }));
1022
- }
1023
- });
1024
- });
1023
+ handleSubmit(submitMeta) {
1024
+ return this._handleSubmit(submitMeta);
1025
1025
  }
1026
1026
  }
1027
1027
  function normalizeError(rawError) {