@tanstack/react-form 0.0.1 → 0.0.3

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.
@@ -1,7 +1,7 @@
1
1
  import type { DeepKeys, DeepValue, RequiredByKey, Updater } from './utils';
2
2
  import type { FormApi, ValidationError } from './FormApi';
3
3
  import { Store } from '@tanstack/store';
4
- declare type ValidateOn = 'change' | 'blur' | 'submit';
4
+ export declare type ValidationCause = 'change' | 'blur' | 'submit';
5
5
  export declare type FieldOptions<TData, TFormData> = {
6
6
  name: unknown extends TFormData ? string : DeepKeys<TFormData>;
7
7
  defaultValue?: TData;
@@ -9,14 +9,10 @@ export declare type FieldOptions<TData, TFormData> = {
9
9
  validate?: (value: TData, fieldApi: FieldApi<TData, TFormData>) => ValidationError;
10
10
  validateAsync?: (value: TData, fieldApi: FieldApi<TData, TFormData>) => ValidationError | Promise<ValidationError>;
11
11
  validatePristine?: boolean;
12
- validateOn?: ValidateOn;
13
- validateAsyncOn?: ValidateOn;
12
+ validateOn?: ValidationCause;
13
+ validateAsyncOn?: ValidationCause;
14
14
  validateAsyncDebounceMs?: number;
15
- filterValue?: (value: TData) => TData;
16
15
  defaultMeta?: Partial<FieldMeta>;
17
- change?: boolean;
18
- blur?: boolean;
19
- submit?: boolean;
20
16
  };
21
17
  export declare type FieldMeta = {
22
18
  isTouched: boolean;
@@ -44,7 +40,7 @@ export declare class FieldApi<TData, TFormData> {
44
40
  name: DeepKeys<TFormData>;
45
41
  store: Store<FieldState<TData>>;
46
42
  state: FieldState<TData>;
47
- options: RequiredByKey<FieldOptions<TData, TFormData>, 'validateOn' | 'validateAsyncOn'>;
43
+ options: RequiredByKey<FieldOptions<TData, TFormData>, 'validatePristine' | 'validateOn' | 'validateAsyncOn' | 'validateAsyncDebounceMs'>;
48
44
  constructor(opts: FieldApiOptions<TData, TFormData>);
49
45
  mount: () => () => void;
50
46
  update: (opts: FieldApiOptions<TData, TFormData>) => void;
@@ -61,9 +57,11 @@ export declare class FieldApi<TData, TFormData> {
61
57
  removeValue: (index: number) => void;
62
58
  swapValues: (aIndex: number, bIndex: number) => void;
63
59
  getSubField: <TName extends DeepKeys<TData>>(name: TName) => FieldApi<DeepValue<TData, TName>, TFormData>;
64
- validate: () => Promise<ValidationError>;
65
- validateAsync: () => Promise<ValidationError>;
60
+ validateSync: (value?: TData) => Promise<void>;
61
+ cancelValidateAsync: () => void;
62
+ validateAsync: (value?: TData) => Promise<ValidationError>;
63
+ shouldValidate: (isAsync: boolean, cause?: ValidationCause) => boolean;
64
+ validate: (cause?: ValidationCause, value?: TData) => Promise<ValidationError>;
66
65
  getChangeProps: <T extends ChangeProps<any>>(props?: T) => ChangeProps<TData> & Omit<T, keyof ChangeProps<TData>>;
67
66
  getInputProps: <T extends InputProps>(props?: T) => InputProps & Omit<T, keyof InputProps>;
68
67
  }
69
- export {};
@@ -1,21 +1,25 @@
1
1
  import type { FormEvent } from 'react';
2
2
  import { Store } from '@tanstack/store';
3
3
  import type { DeepKeys, DeepValue, Updater } from './utils';
4
- import type { FieldApi, FieldMeta } from './FieldApi';
4
+ import type { FieldApi, FieldMeta, ValidationCause } from './FieldApi';
5
5
  export declare type FormOptions<TData> = {
6
6
  defaultValues?: TData;
7
7
  defaultState?: Partial<FormState<TData>>;
8
- onSubmit?: (values: TData, formApi: FormApi<TData>) => Promise<any>;
8
+ onSubmit?: (values: TData, formApi: FormApi<TData>) => void;
9
9
  onInvalidSubmit?: (values: TData, formApi: FormApi<TData>) => void;
10
10
  validate?: (values: TData, formApi: FormApi<TData>) => Promise<any>;
11
11
  debugForm?: boolean;
12
- validatePristine?: boolean;
12
+ defaultValidatePristine?: boolean;
13
+ defaultValidateOn?: ValidationCause;
14
+ defaultValidateAsyncOn?: ValidationCause;
15
+ defaultValidateAsyncDebounceMs?: number;
13
16
  };
14
17
  export declare type FieldInfo<TFormData> = {
15
18
  instances: Record<string, FieldApi<any, TFormData>>;
16
19
  } & ValidationMeta;
17
20
  export declare type ValidationMeta = {
18
21
  validationCount?: number;
22
+ validationAsyncCount?: number;
19
23
  validationPromise?: Promise<ValidationError>;
20
24
  validationResolve?: (error: ValidationError) => void;
21
25
  validationReject?: (error: unknown) => void;
@@ -9,6 +9,7 @@
9
9
  * @license MIT
10
10
  */
11
11
  import { DeepKeys, FieldApi, DeepValue, FieldOptions, FormApi, FormState, FormOptions } from '@tanstack/form-core';
12
+ export * from '@tanstack/form-core';
12
13
  import { NoInfer } from '@tanstack/react-store';
13
14
  import React from 'react';
14
15
 
@@ -1,4 +1,4 @@
1
- import { FormApi } from '@tanstack/form-core';
1
+ import type { FormApi } from '@tanstack/form-core';
2
2
  import * as React from 'react';
3
- export declare const formContext: React.Context<FormApi<any>>;
3
+ export declare const formContext: React.Context<FormApi<any> | null>;
4
4
  export declare function useFormContext(customFormApi?: FormApi<any>): FormApi<any>;
@@ -1,3 +1,4 @@
1
+ export * from '@tanstack/form-core';
1
2
  export * from './useForm';
2
3
  export * from './Field';
3
4
  export * from './useField';
@@ -37,23 +37,6 @@
37
37
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
38
38
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
39
39
 
40
- function _extends() {
41
- _extends = Object.assign ? Object.assign.bind() : function (target) {
42
- for (var i = 1; i < arguments.length; i++) {
43
- var source = arguments[i];
44
-
45
- for (var key in source) {
46
- if (Object.prototype.hasOwnProperty.call(source, key)) {
47
- target[key] = source[key];
48
- }
49
- }
50
- }
51
-
52
- return target;
53
- };
54
- return _extends.apply(this, arguments);
55
- }
56
-
57
40
  /**
58
41
  * store
59
42
  *
@@ -498,7 +481,7 @@
498
481
 
499
482
  var _updateStore = /*#__PURE__*/_classPrivateFieldLooseKey("updateStore");
500
483
 
501
- var _validate = /*#__PURE__*/_classPrivateFieldLooseKey("validate");
484
+ var _leaseValidateAsync = /*#__PURE__*/_classPrivateFieldLooseKey("leaseValidateAsync");
502
485
 
503
486
  class FieldApi {
504
487
  constructor(_opts) {
@@ -545,10 +528,13 @@
545
528
  });
546
529
 
547
530
  this.update = opts => {
531
+ var _this$form$options$de, _this$form$options$de2, _this$form$options$de3, _this$form$options$de4;
532
+
548
533
  this.options = {
549
- validateOn: 'change',
550
- validateAsyncOn: 'blur',
551
- validateAsyncDebounceMs: 0,
534
+ validatePristine: (_this$form$options$de = this.form.options.defaultValidatePristine) != null ? _this$form$options$de : false,
535
+ validateOn: (_this$form$options$de2 = this.form.options.defaultValidateOn) != null ? _this$form$options$de2 : 'change',
536
+ validateAsyncOn: (_this$form$options$de3 = this.form.options.defaultValidateAsyncOn) != null ? _this$form$options$de3 : 'blur',
537
+ validateAsyncDebounceMs: (_this$form$options$de4 = this.form.options.defaultValidateAsyncDebounceMs) != null ? _this$form$options$de4 : 0,
552
538
  ...opts
553
539
  }; // Default Value
554
540
 
@@ -586,48 +572,91 @@
586
572
  form: this.form
587
573
  });
588
574
 
589
- Object.defineProperty(this, _validate, {
590
- writable: true,
591
- value: async isAsync => {
592
- if (!this.options.validate) {
593
- return;
594
- }
575
+ this.validateSync = async (value = this.state.value) => {
576
+ const {
577
+ validate
578
+ } = this.options;
579
+
580
+ if (!validate) {
581
+ return;
582
+ } // Use the validationCount for all field instances to
583
+ // track freshness of the validation
584
+
595
585
 
586
+ const validationCount = (this.getInfo().validationCount || 0) + 1;
587
+ this.getInfo().validationCount = validationCount;
588
+ const error = normalizeError(validate(value, this));
589
+
590
+ if (this.state.meta.error !== error) {
596
591
  this.setMeta(prev => ({ ...prev,
597
- isValidating: true
598
- })); // Use the validationCount for all field instances to
599
- // track freshness of the validation
592
+ error
593
+ }));
594
+ } // If a sync error is encountered, cancel any async validation
600
595
 
601
- const validationCount = (this.getInfo().validationCount || 0) + 1;
602
- this.getInfo().validationCount = validationCount;
603
596
 
604
- const checkLatest = () => validationCount === this.getInfo().validationCount;
597
+ if (this.state.meta.error) {
598
+ this.cancelValidateAsync();
599
+ }
600
+ };
605
601
 
606
- if (!this.getInfo().validationPromise) {
607
- this.getInfo().validationPromise = new Promise((resolve, reject) => {
608
- this.getInfo().validationResolve = resolve;
609
- this.getInfo().validationReject = reject;
610
- });
611
- }
602
+ Object.defineProperty(this, _leaseValidateAsync, {
603
+ writable: true,
604
+ value: () => {
605
+ const count = (this.getInfo().validationAsyncCount || 0) + 1;
606
+ this.getInfo().validationAsyncCount = count;
607
+ return count;
608
+ }
609
+ });
612
610
 
613
- try {
614
- const rawError = await this.options.validate(this.state.value, this);
611
+ this.cancelValidateAsync = () => {
612
+ // Lease a new validation count to ignore any pending validations
613
+ _classPrivateFieldLooseBase(this, _leaseValidateAsync)[_leaseValidateAsync](); // Cancel any pending validation state
615
614
 
616
- if (checkLatest()) {
617
- var _this$getInfo$validat, _this$getInfo;
618
615
 
619
- const error = (() => {
620
- if (rawError) {
621
- if (typeof rawError !== 'string') {
622
- return 'Invalid Form Values';
623
- }
616
+ this.setMeta(prev => ({ ...prev,
617
+ isValidating: false
618
+ }));
619
+ };
624
620
 
625
- return rawError;
626
- }
621
+ this.validateAsync = async (value = this.state.value) => {
622
+ const {
623
+ validateAsync,
624
+ validateAsyncDebounceMs
625
+ } = this.options;
626
+
627
+ if (!validateAsync) {
628
+ return;
629
+ }
627
630
 
628
- return undefined;
629
- })();
631
+ if (this.state.meta.isValidating !== true) this.setMeta(prev => ({ ...prev,
632
+ isValidating: true
633
+ })); // Use the validationCount for all field instances to
634
+ // track freshness of the validation
635
+
636
+ const validationAsyncCount = _classPrivateFieldLooseBase(this, _leaseValidateAsync)[_leaseValidateAsync]();
637
+
638
+ const checkLatest = () => validationAsyncCount === this.getInfo().validationAsyncCount;
639
+
640
+ if (!this.getInfo().validationPromise) {
641
+ this.getInfo().validationPromise = new Promise((resolve, reject) => {
642
+ this.getInfo().validationResolve = resolve;
643
+ this.getInfo().validationReject = reject;
644
+ });
645
+ }
630
646
 
647
+ if (validateAsyncDebounceMs > 0) {
648
+ await new Promise(r => setTimeout(r, validateAsyncDebounceMs));
649
+ } // Only kick off validation if this validation is the latest attempt
650
+
651
+
652
+ if (checkLatest()) {
653
+ try {
654
+ const rawError = await validateAsync(value, this);
655
+
656
+ if (checkLatest()) {
657
+ var _this$getInfo$validat, _this$getInfo;
658
+
659
+ const error = normalizeError(rawError);
631
660
  this.setMeta(prev => ({ ...prev,
632
661
  isValidating: false,
633
662
  error
@@ -649,14 +678,45 @@
649
678
  delete this.getInfo().validationPromise;
650
679
  }
651
680
  }
681
+ } // Always return the latest validation promise to the caller
652
682
 
653
- return this.getInfo().validationPromise;
654
- }
655
- });
656
683
 
657
- this.validate = () => _classPrivateFieldLooseBase(this, _validate)[_validate](false);
684
+ return this.getInfo().validationPromise;
685
+ };
658
686
 
659
- this.validateAsync = () => _classPrivateFieldLooseBase(this, _validate)[_validate](true);
687
+ this.shouldValidate = (isAsync, cause) => {
688
+ const {
689
+ validateOn,
690
+ validateAsyncOn
691
+ } = this.options;
692
+ const level = getValidationCauseLevel(cause); // Must meet *at least* the validation level to validate,
693
+ // e.g. if validateOn is 'change' and validateCause is 'blur',
694
+ // the field will still validate
695
+
696
+ return Object.keys(validateCauseLevels).some(d => isAsync ? validateAsyncOn : validateOn === d && level >= validateCauseLevels[d]);
697
+ };
698
+
699
+ this.validate = async (cause, value) => {
700
+ // If the field is pristine and validatePristine is false, do not validate
701
+ if (!this.options.validatePristine && !this.state.meta.isTouched) return; // Attempt to sync validate first
702
+
703
+ if (this.shouldValidate(false, cause)) {
704
+ this.validateSync(value);
705
+ } // If there is an error, return it, do not attempt async validation
706
+
707
+
708
+ if (this.state.meta.error) {
709
+ return this.state.meta.error;
710
+ } // No error? Attempt async validation
711
+
712
+
713
+ if (this.shouldValidate(true, cause)) {
714
+ return this.validateAsync(value);
715
+ } // If there is no sync error or async validation attempt, there is no error
716
+
717
+
718
+ return undefined;
719
+ };
660
720
 
661
721
  this.getChangeProps = (props = {}) => {
662
722
  return { ...props,
@@ -669,14 +729,7 @@
669
729
  this.setMeta(prev => ({ ...prev,
670
730
  isTouched: true
671
731
  }));
672
- const {
673
- validateOn
674
- } = this.options;
675
-
676
- if (validateOn === 'blur' || validateOn.split('-')[0] === 'blur') {
677
- this.validate();
678
- }
679
-
732
+ this.validate('blur');
680
733
  props.onBlur == null ? void 0 : props.onBlur(e);
681
734
  }
682
735
  };
@@ -715,17 +768,13 @@
715
768
  onUpdate: next => {
716
769
  next.meta.touchedError = next.meta.isTouched ? next.meta.error : undefined; // Do not validate pristine fields
717
770
 
718
- if (!this.options.validatePristine && !next.meta.isTouched) return; // If validateOn is set to a variation of change, run the validation
771
+ const prevState = this.state;
772
+ this.state = next;
719
773
 
720
- if (this.options.validateOn === 'change' || this.options.validateOn.split('-')[0] === 'change') {
721
- try {
722
- this.validate();
723
- } catch (err) {
724
- console.error('An error occurred during validation', err);
725
- }
774
+ if (next.value !== prevState.value) {
775
+ console.log('change');
776
+ this.validate('change', next.value);
726
777
  }
727
-
728
- this.state = next;
729
778
  }
730
779
  });
731
780
  this.state = this.store.state;
@@ -733,6 +782,44 @@
733
782
  }
734
783
 
735
784
  }
785
+ const validateCauseLevels = {
786
+ change: 0,
787
+ blur: 1,
788
+ submit: 2
789
+ };
790
+
791
+ function getValidationCauseLevel(cause) {
792
+ return !cause ? 3 : validateCauseLevels[cause];
793
+ }
794
+
795
+ function normalizeError(rawError) {
796
+ if (rawError) {
797
+ if (typeof rawError !== 'string') {
798
+ return 'Invalid Form Values';
799
+ }
800
+
801
+ return rawError;
802
+ }
803
+
804
+ return undefined;
805
+ }
806
+
807
+ function _extends() {
808
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
809
+ for (var i = 1; i < arguments.length; i++) {
810
+ var source = arguments[i];
811
+
812
+ for (var key in source) {
813
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
814
+ target[key] = source[key];
815
+ }
816
+ }
817
+ }
818
+
819
+ return target;
820
+ };
821
+ return _extends.apply(this, arguments);
822
+ }
736
823
 
737
824
  /**
738
825
  * react-store
@@ -895,9 +982,15 @@
895
982
  }
896
983
 
897
984
  exports.Field = Field;
985
+ exports.FieldApi = FieldApi;
986
+ exports.FormApi = FormApi;
898
987
  exports.createFieldComponent = createFieldComponent;
899
988
  exports.createFormComponent = createFormComponent;
900
989
  exports.createUseField = createUseField;
990
+ exports.functionalUpdate = functionalUpdate;
991
+ exports.getBy = getBy;
992
+ exports.getDefaultFormState = getDefaultFormState;
993
+ exports.setBy = setBy;
901
994
  exports.useField = useField;
902
995
  exports.useForm = useForm;
903
996