@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/dist/cjs/FieldApi.cjs +24 -49
- package/dist/cjs/FieldApi.cjs.map +1 -1
- package/dist/cjs/FieldApi.d.cts +18 -26
- package/dist/cjs/FormApi.cjs +155 -105
- package/dist/cjs/FormApi.cjs.map +1 -1
- package/dist/cjs/FormApi.d.cts +47 -48
- package/dist/esm/FieldApi.d.ts +18 -26
- package/dist/esm/FieldApi.js +25 -50
- package/dist/esm/FieldApi.js.map +1 -1
- package/dist/esm/FormApi.d.ts +47 -48
- package/dist/esm/FormApi.js +156 -106
- package/dist/esm/FormApi.js.map +1 -1
- package/package.json +2 -2
- package/src/FieldApi.ts +41 -82
- package/src/FormApi.ts +241 -168
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
|
|
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
|
-
|
|
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
|
|
318
|
+
* A record of field metadata for each field in the form.
|
|
312
319
|
*/
|
|
313
|
-
|
|
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
|
-
):
|
|
328
|
+
): BaseFormState<TFormData> {
|
|
319
329
|
return {
|
|
320
330
|
values: defaultState.values ?? ({} as never),
|
|
321
|
-
errors: defaultState.errors ?? [],
|
|
322
331
|
errorMap: defaultState.errorMap ?? {},
|
|
323
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
412
|
-
|
|
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
|
-
|
|
416
|
-
|
|
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
|
-
|
|
422
|
-
|
|
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
|
-
|
|
426
|
-
state.errorMap.onMount = undefined
|
|
427
|
-
}
|
|
470
|
+
isTouched && currBaseStore?.errorMap?.onMount
|
|
428
471
|
|
|
429
|
-
|
|
430
|
-
|
|
472
|
+
const isDirty = fieldMetaValues.some((field) => field?.isDirty)
|
|
473
|
+
const isPristine = !isDirty
|
|
431
474
|
|
|
432
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
fieldMetaValues.some((f) => f?.errorMap?.onMount),
|
|
437
|
-
)
|
|
479
|
+
fieldMetaValues.some((f) => f?.errorMap?.onMount),
|
|
480
|
+
)
|
|
438
481
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
474
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1028
|
+
this.baseStore.setState((d) => ({ ...d, isSubmitting: true }))
|
|
959
1029
|
|
|
960
1030
|
const done = () => {
|
|
961
|
-
this.
|
|
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
|
-
|
|
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
|
-
|
|
993
|
-
this.
|
|
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.
|
|
1114
|
+
this.baseStore.setState((prev) => {
|
|
1045
1115
|
return {
|
|
1046
1116
|
...prev,
|
|
1047
|
-
|
|
1048
|
-
...prev.
|
|
1049
|
-
[field]: functionalUpdate(
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1183
|
+
this.baseStore.setState((prev) => {
|
|
1111
1184
|
const newState = { ...prev }
|
|
1112
1185
|
newState.values = deleteBy(newState.values, field)
|
|
1113
|
-
delete newState.
|
|
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.
|
|
1357
|
+
this.baseStore.setState((prev) => ({
|
|
1285
1358
|
...prev,
|
|
1286
1359
|
errorMap: {
|
|
1287
1360
|
...prev.errorMap,
|