@tanstack/form-core 0.40.4 → 0.41.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.
@@ -1,24 +1,13 @@
1
- import { Store } from "@tanstack/store";
1
+ import { batch, Store, Derived } from "@tanstack/store";
2
2
  import { getSyncValidatorArray, getAsyncValidatorArray, getBy, functionalUpdate, setBy, deleteBy, isNonEmptyArray } from "./utils.js";
3
3
  import { isStandardSchemaValidator, standardSchemaValidator } from "./standardSchemaValidator.js";
4
4
  function getDefaultFormState(defaultState) {
5
5
  return {
6
6
  values: defaultState.values ?? {},
7
- errors: defaultState.errors ?? [],
8
7
  errorMap: defaultState.errorMap ?? {},
9
- fieldMeta: defaultState.fieldMeta ?? {},
10
- canSubmit: defaultState.canSubmit ?? true,
11
- isFieldsValid: defaultState.isFieldsValid ?? false,
12
- isFieldsValidating: defaultState.isFieldsValidating ?? false,
13
- isFormValid: defaultState.isFormValid ?? false,
14
- isFormValidating: defaultState.isFormValidating ?? false,
8
+ fieldMetaBase: defaultState.fieldMetaBase ?? {},
15
9
  isSubmitted: defaultState.isSubmitted ?? false,
16
10
  isSubmitting: defaultState.isSubmitting ?? false,
17
- isTouched: defaultState.isTouched ?? false,
18
- isBlurred: defaultState.isBlurred ?? false,
19
- isPristine: defaultState.isPristine ?? true,
20
- isDirty: defaultState.isDirty ?? false,
21
- isValid: defaultState.isValid ?? false,
22
11
  isValidating: defaultState.isValidating ?? false,
23
12
  submissionAttempts: defaultState.submissionAttempts ?? 0,
24
13
  validationMetaMap: defaultState.validationMetaMap ?? {
@@ -43,18 +32,25 @@ class FormApi {
43
32
  this.fieldInfo = {};
44
33
  this.prevTransformArray = [];
45
34
  this.mount = () => {
35
+ const cleanupFieldMetaDerived = this.fieldMetaDerived.mount();
36
+ const cleanupStoreDerived = this.store.mount();
37
+ const cleanup = () => {
38
+ cleanupFieldMetaDerived();
39
+ cleanupStoreDerived();
40
+ };
46
41
  const { onMount } = this.options.validators || {};
47
- if (!onMount) return;
42
+ if (!onMount) return cleanup;
48
43
  this.validateSync("mount");
44
+ return cleanup;
49
45
  };
50
46
  this.update = (options) => {
51
47
  if (!options) return;
52
48
  const oldOptions = this.options;
53
49
  this.options = options;
54
- this.store.batch(() => {
50
+ batch(() => {
55
51
  const shouldUpdateValues = options.defaultValues && options.defaultValues !== oldOptions.defaultValues && !this.state.isTouched;
56
52
  const shouldUpdateState = options.defaultState !== oldOptions.defaultState && !this.state.isTouched;
57
- this.store.setState(
53
+ this.baseStore.setState(
58
54
  () => getDefaultFormState(
59
55
  Object.assign(
60
56
  {},
@@ -70,27 +66,27 @@ class FormApi {
70
66
  };
71
67
  this.reset = (values, opts2) => {
72
68
  const { fieldMeta: currentFieldMeta } = this.state;
73
- const fieldMeta = this.resetFieldMeta(currentFieldMeta);
69
+ const fieldMetaBase = this.resetFieldMeta(currentFieldMeta);
74
70
  if (values && !(opts2 == null ? void 0 : opts2.keepDefaultValues)) {
75
71
  this.options = {
76
72
  ...this.options,
77
73
  defaultValues: values
78
74
  };
79
75
  }
80
- this.store.setState(
76
+ this.baseStore.setState(
81
77
  () => {
82
78
  var _a2;
83
79
  return getDefaultFormState({
84
80
  ...this.options.defaultState,
85
81
  values: values ?? this.options.defaultValues ?? ((_a2 = this.options.defaultState) == null ? void 0 : _a2.values),
86
- fieldMeta
82
+ fieldMetaBase
87
83
  });
88
84
  }
89
85
  );
90
86
  };
91
87
  this.validateAllFields = async (cause) => {
92
88
  const fieldValidationPromises = [];
93
- this.store.batch(() => {
89
+ batch(() => {
94
90
  void Object.values(this.fieldInfo).forEach((field) => {
95
91
  if (!field.instance) return;
96
92
  const fieldInstance = field.instance;
@@ -117,7 +113,7 @@ class FormApi {
117
113
  (fieldKey) => fieldKeysToValidate.some((key) => fieldKey.startsWith(key))
118
114
  );
119
115
  const fieldValidationPromises = [];
120
- this.store.batch(() => {
116
+ batch(() => {
121
117
  fieldsToValidate.forEach((nestedField) => {
122
118
  fieldValidationPromises.push(
123
119
  Promise.resolve().then(() => this.validateField(nestedField, cause))
@@ -140,7 +136,7 @@ class FormApi {
140
136
  const validates = getSyncValidatorArray(cause, this.options);
141
137
  let hasErrored = false;
142
138
  const fieldsErrorMap = {};
143
- this.store.batch(() => {
139
+ batch(() => {
144
140
  for (const validateObj of validates) {
145
141
  if (!validateObj.validate) continue;
146
142
  const rawError = this.runValidator({
@@ -175,7 +171,7 @@ class FormApi {
175
171
  }
176
172
  }
177
173
  if (this.state.errorMap[errorMapKey] !== formError) {
178
- this.store.setState((prev) => ({
174
+ this.baseStore.setState((prev) => ({
179
175
  ...prev,
180
176
  errorMap: {
181
177
  ...prev.errorMap,
@@ -190,7 +186,7 @@ class FormApi {
190
186
  });
191
187
  const submitErrKey = getErrorMapKey("submit");
192
188
  if (this.state.errorMap[submitErrKey] && cause !== "submit" && !hasErrored) {
193
- this.store.setState((prev) => ({
189
+ this.baseStore.setState((prev) => ({
194
190
  ...prev,
195
191
  errorMap: {
196
192
  ...prev.errorMap,
@@ -203,7 +199,7 @@ class FormApi {
203
199
  this.validateAsync = async (cause) => {
204
200
  const validates = getAsyncValidatorArray(cause, this.options);
205
201
  if (!this.state.isFormValidating) {
206
- this.store.setState((prev) => ({ ...prev, isFormValidating: true }));
202
+ this.baseStore.setState((prev) => ({ ...prev, isFormValidating: true }));
207
203
  }
208
204
  const promises = [];
209
205
  let fieldErrors;
@@ -263,7 +259,7 @@ class FormApi {
263
259
  }
264
260
  }
265
261
  }
266
- this.store.setState((prev) => ({
262
+ this.baseStore.setState((prev) => ({
267
263
  ...prev,
268
264
  errorMap: {
269
265
  ...prev.errorMap,
@@ -294,7 +290,7 @@ class FormApi {
294
290
  }
295
291
  }
296
292
  }
297
- this.store.setState((prev) => ({
293
+ this.baseStore.setState((prev) => ({
298
294
  ...prev,
299
295
  isFormValidating: false
300
296
  }));
@@ -309,7 +305,7 @@ class FormApi {
309
305
  };
310
306
  this.handleSubmit = async () => {
311
307
  var _a2, _b, _c, _d;
312
- this.store.setState((old) => ({
308
+ this.baseStore.setState((old) => ({
313
309
  ...old,
314
310
  // Submission attempts mark the form as not submitted
315
311
  isSubmitted: false,
@@ -317,9 +313,9 @@ class FormApi {
317
313
  submissionAttempts: old.submissionAttempts + 1
318
314
  }));
319
315
  if (!this.state.canSubmit) return;
320
- this.store.setState((d) => ({ ...d, isSubmitting: true }));
316
+ this.baseStore.setState((d) => ({ ...d, isSubmitting: true }));
321
317
  const done = () => {
322
- this.store.setState((prev) => ({ ...prev, isSubmitting: false }));
318
+ this.baseStore.setState((prev) => ({ ...prev, isSubmitting: false }));
323
319
  };
324
320
  await this.validateAllFields("submit");
325
321
  if (!this.state.isValid) {
@@ -330,7 +326,7 @@ class FormApi {
330
326
  });
331
327
  return;
332
328
  }
333
- this.store.batch(() => {
329
+ batch(() => {
334
330
  void Object.values(this.fieldInfo).forEach((field) => {
335
331
  var _a3, _b2, _c2;
336
332
  (_c2 = (_b2 = (_a3 = field.instance) == null ? void 0 : _a3.options.listeners) == null ? void 0 : _b2.onSubmit) == null ? void 0 : _c2.call(_b2, {
@@ -341,8 +337,8 @@ class FormApi {
341
337
  });
342
338
  try {
343
339
  await ((_d = (_c = this.options).onSubmit) == null ? void 0 : _d.call(_c, { value: this.state.values, formApi: this }));
344
- this.store.batch(() => {
345
- this.store.setState((prev) => ({ ...prev, isSubmitted: true }));
340
+ batch(() => {
341
+ this.baseStore.setState((prev) => ({ ...prev, isSubmitted: true }));
346
342
  done();
347
343
  });
348
344
  } catch (err) {
@@ -368,12 +364,15 @@ class FormApi {
368
364
  });
369
365
  };
370
366
  this.setFieldMeta = (field, updater) => {
371
- this.store.setState((prev) => {
367
+ this.baseStore.setState((prev) => {
372
368
  return {
373
369
  ...prev,
374
- fieldMeta: {
375
- ...prev.fieldMeta,
376
- [field]: functionalUpdate(updater, prev.fieldMeta[field])
370
+ fieldMetaBase: {
371
+ ...prev.fieldMetaBase,
372
+ [field]: functionalUpdate(
373
+ updater,
374
+ prev.fieldMetaBase[field]
375
+ )
377
376
  }
378
377
  };
379
378
  });
@@ -398,7 +397,7 @@ class FormApi {
398
397
  };
399
398
  this.setFieldValue = (field, updater, opts2) => {
400
399
  const dontUpdateMeta = (opts2 == null ? void 0 : opts2.dontUpdateMeta) ?? false;
401
- this.store.batch(() => {
400
+ batch(() => {
402
401
  if (!dontUpdateMeta) {
403
402
  this.setFieldMeta(field, (prev) => ({
404
403
  ...prev,
@@ -411,7 +410,7 @@ class FormApi {
411
410
  }
412
411
  }));
413
412
  }
414
- this.store.setState((prev) => {
413
+ this.baseStore.setState((prev) => {
415
414
  return {
416
415
  ...prev,
417
416
  values: setBy(prev.values, field, updater)
@@ -420,10 +419,10 @@ class FormApi {
420
419
  });
421
420
  };
422
421
  this.deleteField = (field) => {
423
- this.store.setState((prev) => {
422
+ this.baseStore.setState((prev) => {
424
423
  const newState = { ...prev };
425
424
  newState.values = deleteBy(newState.values, field);
426
- delete newState.fieldMeta[field];
425
+ delete newState.fieldMetaBase[field];
427
426
  return newState;
428
427
  });
429
428
  delete this.fieldInfo[field];
@@ -512,80 +511,131 @@ class FormApi {
512
511
  this.validateField(`${field}[${index1}]`, "change");
513
512
  this.validateField(`${field}[${index2}]`, "change");
514
513
  };
515
- this.store = new Store(
514
+ this.baseStore = new Store(
516
515
  getDefaultFormState({
517
516
  ...opts == null ? void 0 : opts.defaultState,
518
517
  values: (opts == null ? void 0 : opts.defaultValues) ?? ((_a = opts == null ? void 0 : opts.defaultState) == null ? void 0 : _a.values),
519
518
  isFormValid: true
520
- }),
521
- {
522
- onUpdate: () => {
523
- var _a2, _b, _c, _d;
524
- let { state } = this.store;
525
- const fieldMetaValues = Object.values(state.fieldMeta);
526
- const isFieldsValidating = fieldMetaValues.some(
527
- (field) => field == null ? void 0 : field.isValidating
528
- );
529
- const isFieldsValid = !fieldMetaValues.some(
530
- (field) => (field == null ? void 0 : field.errorMap) && isNonEmptyArray(Object.values(field.errorMap).filter(Boolean))
531
- );
532
- const isTouched = fieldMetaValues.some((field) => field == null ? void 0 : field.isTouched);
533
- const isBlurred = fieldMetaValues.some((field) => field == null ? void 0 : field.isBlurred);
534
- if (isTouched && ((_a2 = state == null ? void 0 : state.errorMap) == null ? void 0 : _a2.onMount)) {
535
- state.errorMap.onMount = void 0;
519
+ })
520
+ );
521
+ this.fieldMetaDerived = new Derived({
522
+ deps: [this.baseStore],
523
+ fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => {
524
+ var _a2;
525
+ const prevVal = _prevVal;
526
+ const prevBaseStore = prevDepVals == null ? void 0 : prevDepVals[0];
527
+ const currBaseStore = currDepVals[0];
528
+ const fieldMeta = {};
529
+ for (const fieldName of Object.keys(
530
+ currBaseStore.fieldMetaBase
531
+ )) {
532
+ const currBaseVal = currBaseStore.fieldMetaBase[fieldName];
533
+ const prevBaseVal = prevBaseStore == null ? void 0 : prevBaseStore.fieldMetaBase[fieldName];
534
+ let fieldErrors = (_a2 = prevVal == null ? void 0 : prevVal[fieldName]) == null ? void 0 : _a2.errors;
535
+ if (!prevBaseVal || currBaseVal.errorMap !== prevBaseVal.errorMap) {
536
+ fieldErrors = Object.values(currBaseVal.errorMap ?? {}).filter(
537
+ (val) => val !== void 0
538
+ );
536
539
  }
537
- const isDirty = fieldMetaValues.some((field) => field == null ? void 0 : field.isDirty);
538
- const isPristine = !isDirty;
539
- const hasOnMountError = Boolean(
540
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
541
- ((_b = state.errorMap) == null ? void 0 : _b.onMount) || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
542
- fieldMetaValues.some((f) => {
543
- var _a3;
544
- return (_a3 = f == null ? void 0 : f.errorMap) == null ? void 0 : _a3.onMount;
545
- })
546
- );
547
- const isValidating = isFieldsValidating || state.isFormValidating;
548
- state.errors = Object.values(state.errorMap).reduce((prev, curr) => {
549
- if (curr === void 0) return prev;
550
- if (typeof curr === "string") {
551
- prev.push(curr);
552
- return prev;
553
- } else if (curr && isFormValidationError(curr)) {
554
- prev.push(curr.form);
555
- return prev;
556
- }
557
- return prev;
558
- }, []);
559
- const isFormValid = state.errors.length === 0;
560
- const isValid = isFieldsValid && isFormValid;
561
- const canSubmit = state.submissionAttempts === 0 && !isTouched && !hasOnMountError || !isValidating && !state.isSubmitting && isValid;
562
- state = {
563
- ...state,
564
- isFieldsValidating,
565
- isFieldsValid,
566
- isFormValid,
567
- isValid,
568
- canSubmit,
569
- isTouched,
570
- isBlurred,
571
- isPristine,
572
- isDirty
540
+ const isFieldPristine = !currBaseVal.isDirty;
541
+ fieldMeta[fieldName] = {
542
+ ...currBaseVal,
543
+ errors: fieldErrors,
544
+ isPristine: isFieldPristine
573
545
  };
574
- this.state = state;
575
- this.store.state = this.state;
576
- const transformArray = ((_c = this.options.transform) == null ? void 0 : _c.deps) ?? [];
577
- const shouldTransform = transformArray.length !== this.prevTransformArray.length || transformArray.some((val, i) => val !== this.prevTransformArray[i]);
578
- if (shouldTransform) {
579
- (_d = this.options.transform) == null ? void 0 : _d.fn(this);
580
- this.store.state = this.state;
581
- this.prevTransformArray = transformArray;
582
- }
583
546
  }
547
+ return fieldMeta;
584
548
  }
585
- );
586
- this.state = this.store.state;
549
+ });
550
+ this.store = new Derived({
551
+ deps: [this.baseStore, this.fieldMetaDerived],
552
+ fn: ({ prevDepVals, currDepVals, prevVal: _prevVal }) => {
553
+ var _a2, _b, _c, _d;
554
+ const prevVal = _prevVal;
555
+ const prevBaseStore = prevDepVals == null ? void 0 : prevDepVals[0];
556
+ const currBaseStore = currDepVals[0];
557
+ const fieldMetaValues = Object.values(currBaseStore.fieldMetaBase);
558
+ const isFieldsValidating = fieldMetaValues.some(
559
+ (field) => field == null ? void 0 : field.isValidating
560
+ );
561
+ const isFieldsValid = !fieldMetaValues.some(
562
+ (field) => (field == null ? void 0 : field.errorMap) && isNonEmptyArray(Object.values(field.errorMap).filter(Boolean))
563
+ );
564
+ const isTouched = fieldMetaValues.some((field) => field == null ? void 0 : field.isTouched);
565
+ const isBlurred = fieldMetaValues.some((field) => field == null ? void 0 : field.isBlurred);
566
+ const shouldInvalidateOnMount = (
567
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
568
+ isTouched && ((_a2 = currBaseStore == null ? void 0 : currBaseStore.errorMap) == null ? void 0 : _a2.onMount)
569
+ );
570
+ const isDirty = fieldMetaValues.some((field) => field == null ? void 0 : field.isDirty);
571
+ const isPristine = !isDirty;
572
+ const hasOnMountError = Boolean(
573
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
574
+ ((_b = currBaseStore.errorMap) == null ? void 0 : _b.onMount) || // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
575
+ fieldMetaValues.some((f) => {
576
+ var _a3;
577
+ return (_a3 = f == null ? void 0 : f.errorMap) == null ? void 0 : _a3.onMount;
578
+ })
579
+ );
580
+ const isValidating = !!isFieldsValidating;
581
+ let errors = (prevVal == null ? void 0 : prevVal.errors) ?? [];
582
+ if (!prevBaseStore || currBaseStore.errorMap !== prevBaseStore.errorMap) {
583
+ errors = Object.values(currBaseStore.errorMap).reduce(
584
+ (prev, curr) => {
585
+ if (curr === void 0) return prev;
586
+ if (typeof curr === "string") {
587
+ prev.push(curr);
588
+ return prev;
589
+ } else if (curr && isFormValidationError(curr)) {
590
+ prev.push(curr.form);
591
+ return prev;
592
+ }
593
+ return prev;
594
+ },
595
+ []
596
+ );
597
+ }
598
+ const isFormValid = errors.length === 0;
599
+ const isValid = isFieldsValid && isFormValid;
600
+ const canSubmit = currBaseStore.submissionAttempts === 0 && !isTouched && !hasOnMountError || !isValidating && !currBaseStore.isSubmitting && isValid;
601
+ let errorMap = currBaseStore.errorMap;
602
+ if (shouldInvalidateOnMount) {
603
+ errors = errors.filter(
604
+ (err) => err !== currBaseStore.errorMap.onMount
605
+ );
606
+ errorMap = Object.assign(errorMap, { onMount: void 0 });
607
+ }
608
+ let state = {
609
+ ...currBaseStore,
610
+ errorMap,
611
+ fieldMeta: this.fieldMetaDerived.state,
612
+ errors,
613
+ isFieldsValidating,
614
+ isFieldsValid,
615
+ isFormValid,
616
+ isValid,
617
+ canSubmit,
618
+ isTouched,
619
+ isBlurred,
620
+ isPristine,
621
+ isDirty
622
+ };
623
+ const transformArray = ((_c = this.options.transform) == null ? void 0 : _c.deps) ?? [];
624
+ const shouldTransform = transformArray.length !== this.prevTransformArray.length || transformArray.some((val, i) => val !== this.prevTransformArray[i]);
625
+ if (shouldTransform) {
626
+ const newObj = Object.assign({}, this, { state });
627
+ (_d = this.options.transform) == null ? void 0 : _d.fn(newObj);
628
+ state = newObj.state;
629
+ this.prevTransformArray = transformArray;
630
+ }
631
+ return state;
632
+ }
633
+ });
587
634
  this.update(opts || {});
588
635
  }
636
+ get state() {
637
+ return this.store.state;
638
+ }
589
639
  /**
590
640
  * @private
591
641
  */
@@ -606,7 +656,7 @@ class FormApi {
606
656
  * Updates the form's errorMap
607
657
  */
608
658
  setErrorMap(errorMap) {
609
- this.store.setState((prev) => ({
659
+ this.baseStore.setState((prev) => ({
610
660
  ...prev,
611
661
  errorMap: {
612
662
  ...prev.errorMap,