@tanstack/form-core 0.7.2 → 0.8.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.
@@ -4,6 +4,8 @@ import { functionalUpdate, getBy, isNonEmptyArray, setBy } from "./utils.js";
4
4
  function getDefaultFormState(defaultState) {
5
5
  return {
6
6
  values: defaultState.values ?? {},
7
+ errors: defaultState.errors ?? [],
8
+ errorMap: defaultState.errorMap ?? {},
7
9
  fieldMeta: defaultState.fieldMeta ?? {},
8
10
  canSubmit: defaultState.canSubmit ?? true,
9
11
  isFieldsValid: defaultState.isFieldsValid ?? false,
@@ -26,14 +28,23 @@ var FormApi = class {
26
28
  this.fieldInfo = {};
27
29
  this.validationMeta = {};
28
30
  this.mount = () => {
29
- if (typeof this.options.onMount === "function") {
30
- return this.options.onMount(this.state.values, this);
31
- }
32
- if (this.options.validator) {
33
- return this.options.validator().validate(
34
- this.state.values,
35
- this.options.onMount
36
- );
31
+ const doValidate = () => {
32
+ if (typeof this.options.onMount === "function") {
33
+ return this.options.onMount(this.state.values, this);
34
+ }
35
+ if (this.options.validator) {
36
+ return this.options.validator().validate(
37
+ this.state.values,
38
+ this.options.onMount
39
+ );
40
+ }
41
+ };
42
+ const error = doValidate();
43
+ if (error) {
44
+ this.store.setState((prev) => ({
45
+ ...prev,
46
+ errorMap: { ...prev.errorMap, onMount: error }
47
+ }));
37
48
  }
38
49
  };
39
50
  this.update = (options) => {
@@ -81,6 +92,132 @@ var FormApi = class {
81
92
  });
82
93
  return Promise.all(fieldValidationPromises);
83
94
  };
95
+ this.validateSync = (cause) => {
96
+ const { onChange, onBlur } = this.options;
97
+ const validate = cause === "change" ? onChange : cause === "blur" ? onBlur : void 0;
98
+ if (!validate)
99
+ return;
100
+ const errorMapKey = getErrorMapKey(cause);
101
+ const doValidate = () => {
102
+ if (typeof validate === "function") {
103
+ return validate(this.state.values, this);
104
+ }
105
+ if (this.options.validator && typeof validate !== "function") {
106
+ return this.options.validator().validate(
107
+ this.state.values,
108
+ validate
109
+ );
110
+ }
111
+ throw new Error(
112
+ `Form validation for ${errorMapKey} failed. ${errorMapKey} should either be a function, or \`validator\` should be correct.`
113
+ );
114
+ };
115
+ const error = normalizeError(doValidate());
116
+ if (this.state.errorMap[errorMapKey] !== error) {
117
+ this.store.setState((prev) => ({
118
+ ...prev,
119
+ errorMap: {
120
+ ...prev.errorMap,
121
+ [errorMapKey]: error
122
+ }
123
+ }));
124
+ }
125
+ if (this.state.errorMap[errorMapKey]) {
126
+ this.cancelValidateAsync();
127
+ }
128
+ };
129
+ this.__leaseValidateAsync = () => {
130
+ const count = (this.validationMeta.validationAsyncCount || 0) + 1;
131
+ this.validationMeta.validationAsyncCount = count;
132
+ return count;
133
+ };
134
+ this.cancelValidateAsync = () => {
135
+ this.__leaseValidateAsync();
136
+ this.store.setState((prev) => ({
137
+ ...prev,
138
+ isFormValidating: false
139
+ }));
140
+ };
141
+ this.validateAsync = async (cause) => {
142
+ const {
143
+ onChangeAsync,
144
+ onBlurAsync,
145
+ asyncDebounceMs,
146
+ onBlurAsyncDebounceMs,
147
+ onChangeAsyncDebounceMs
148
+ } = this.options;
149
+ const validate = cause === "change" ? onChangeAsync : cause === "blur" ? onBlurAsync : void 0;
150
+ if (!validate)
151
+ return [];
152
+ const debounceMs = (cause === "change" ? onChangeAsyncDebounceMs : onBlurAsyncDebounceMs) ?? asyncDebounceMs ?? 0;
153
+ if (!this.state.isFormValidating) {
154
+ this.store.setState((prev) => ({ ...prev, isFormValidating: true }));
155
+ }
156
+ const validationAsyncCount = this.__leaseValidateAsync();
157
+ const checkLatest = () => validationAsyncCount === this.validationMeta.validationAsyncCount;
158
+ if (!this.validationMeta.validationPromise) {
159
+ this.validationMeta.validationPromise = new Promise((resolve, reject) => {
160
+ this.validationMeta.validationResolve = resolve;
161
+ this.validationMeta.validationReject = reject;
162
+ });
163
+ }
164
+ if (debounceMs > 0) {
165
+ await new Promise((r) => setTimeout(r, debounceMs));
166
+ }
167
+ const doValidate = () => {
168
+ if (typeof validate === "function") {
169
+ return validate(this.state.values, this);
170
+ }
171
+ if (this.options.validator && typeof validate !== "function") {
172
+ return this.options.validator().validateAsync(
173
+ this.state.values,
174
+ validate
175
+ );
176
+ }
177
+ const errorMapKey = getErrorMapKey(cause);
178
+ throw new Error(
179
+ `Form validation for ${errorMapKey}Async failed. ${errorMapKey}Async should either be a function, or \`validator\` should be correct.`
180
+ );
181
+ };
182
+ if (checkLatest()) {
183
+ const prevErrors = this.state.errors;
184
+ try {
185
+ const rawError = await doValidate();
186
+ if (checkLatest()) {
187
+ const error = normalizeError(rawError);
188
+ this.store.setState((prev) => ({
189
+ ...prev,
190
+ isFormValidating: false,
191
+ errorMap: {
192
+ ...prev.errorMap,
193
+ [getErrorMapKey(cause)]: error
194
+ }
195
+ }));
196
+ this.validationMeta.validationResolve?.([...prevErrors, error]);
197
+ }
198
+ } catch (error) {
199
+ if (checkLatest()) {
200
+ this.validationMeta.validationReject?.([...prevErrors, error]);
201
+ throw error;
202
+ }
203
+ } finally {
204
+ if (checkLatest()) {
205
+ this.store.setState((prev) => ({ ...prev, isFormValidating: false }));
206
+ delete this.validationMeta.validationPromise;
207
+ }
208
+ }
209
+ }
210
+ return await this.validationMeta.validationPromise ?? [];
211
+ };
212
+ this.validate = (cause) => {
213
+ const errorMapKey = getErrorMapKey(cause);
214
+ const prevError = this.state.errorMap[errorMapKey];
215
+ this.validateSync(cause);
216
+ const newError = this.state.errorMap[errorMapKey];
217
+ if (prevError !== newError && !this.options.asyncAlways && !(newError === void 0 && prevError !== void 0))
218
+ return this.state.errors;
219
+ return this.validateAsync(cause);
220
+ };
84
221
  this.handleSubmit = async () => {
85
222
  this.store.setState((old) => ({
86
223
  ...old,
@@ -101,6 +238,7 @@ var FormApi = class {
101
238
  this.options.onSubmitInvalid?.(this.state.values, this);
102
239
  return;
103
240
  }
241
+ await this.validate("submit");
104
242
  if (!this.state.isValid) {
105
243
  done();
106
244
  this.options.onSubmitInvalid?.(this.state.values, this);
@@ -214,7 +352,10 @@ var FormApi = class {
214
352
  );
215
353
  const isTouched = fieldMetaValues.some((field) => field?.isTouched);
216
354
  const isValidating = isFieldsValidating || state.isFormValidating;
217
- const isFormValid = !state.formError;
355
+ state.errors = Object.values(state.errorMap).filter(
356
+ (val) => val !== void 0
357
+ );
358
+ const isFormValid = state.errors.length === 0;
218
359
  const isValid = isFieldsValid && isFormValid;
219
360
  const canSubmit = state.submissionAttempts === 0 && !isTouched || !isValidating && !state.isSubmitting && isValid;
220
361
  state = {
@@ -235,6 +376,27 @@ var FormApi = class {
235
376
  this.update(opts || {});
236
377
  }
237
378
  };
379
+ function normalizeError(rawError) {
380
+ if (rawError) {
381
+ if (typeof rawError !== "string") {
382
+ return "Invalid Form Values";
383
+ }
384
+ return rawError;
385
+ }
386
+ return void 0;
387
+ }
388
+ function getErrorMapKey(cause) {
389
+ switch (cause) {
390
+ case "submit":
391
+ return "onSubmit";
392
+ case "change":
393
+ return "onChange";
394
+ case "blur":
395
+ return "onBlur";
396
+ case "mount":
397
+ return "onMount";
398
+ }
399
+ }
238
400
  export {
239
401
  FormApi
240
402
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[]>\n validationResolve?: (errors: ValidationError[]) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n formError?: ValidationError\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n const isFormValid = !state.formError\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n // await this.validateForm()\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n const newState = { ...this.state }\n delete newState.values[field as keyof TFormData]\n delete newState.fieldMeta[field]\n\n this.store.setState((_) => newState)\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/FormApi.ts"],"sourcesContent":["import { Store } from '@tanstack/store'\nimport type { DeepKeys, DeepValue, Updater } from './utils'\nimport { functionalUpdate, getBy, isNonEmptyArray, setBy } from './utils'\nimport type { FieldApi, FieldMeta, ValidationCause } from './FieldApi'\nimport type { ValidationError, Validator } from './types'\n\ntype ValidateFn<TData, ValidatorType> = (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n) => ValidationError\n\ntype ValidateOrFn<TData, ValidatorType> = ValidatorType extends Validator<TData>\n ? Parameters<ReturnType<ValidatorType>['validate']>[1]\n : ValidateFn<TData, ValidatorType>\n\ntype ValidateAsyncFn<TData, ValidatorType> = (\n value: TData,\n fieldApi: FormApi<TData, ValidatorType>,\n) => ValidationError | Promise<ValidationError>\n\nexport type FormOptions<TData, ValidatorType> = {\n defaultValues?: TData\n defaultState?: Partial<FormState<TData>>\n asyncAlways?: boolean\n asyncDebounceMs?: number\n validator?: ValidatorType\n onMount?: ValidateOrFn<TData, ValidatorType>\n onChange?: ValidateOrFn<TData, ValidatorType>\n onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>\n onChangeAsyncDebounceMs?: number\n onBlur?: ValidateOrFn<TData, ValidatorType>\n onBlurAsync?: ValidateAsyncFn<TData, ValidatorType>\n onBlurAsyncDebounceMs?: number\n onSubmit?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => any | Promise<any>\n onSubmitInvalid?: (\n values: TData,\n formApi: FormApi<TData, ValidatorType>,\n ) => void\n}\n\nexport type FieldInfo<TFormData, ValidatorType> = {\n instances: Record<string, FieldApi<TFormData, any, unknown, ValidatorType>>\n} & ValidationMeta\n\nexport type ValidationMeta = {\n validationCount?: number\n validationAsyncCount?: number\n validationPromise?: Promise<ValidationError[] | undefined>\n validationResolve?: (errors: ValidationError[] | undefined) => void\n validationReject?: (errors: unknown) => void\n}\n\nexport type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`\n\nexport type ValidationErrorMap = {\n [K in ValidationErrorMapKeys]?: ValidationError\n}\n\nexport type FormState<TData> = {\n values: TData\n // Form Validation\n isFormValidating: boolean\n formValidationCount: number\n isFormValid: boolean\n errors: ValidationError[]\n errorMap: ValidationErrorMap\n // Fields\n fieldMeta: Record<DeepKeys<TData>, FieldMeta>\n isFieldsValidating: boolean\n isFieldsValid: boolean\n isSubmitting: boolean\n // General\n isTouched: boolean\n isSubmitted: boolean\n isValidating: boolean\n isValid: boolean\n canSubmit: boolean\n submissionAttempts: number\n}\n\nfunction getDefaultFormState<TData>(\n defaultState: Partial<FormState<TData>>,\n): FormState<TData> {\n return {\n values: defaultState.values ?? ({} as never),\n errors: defaultState.errors ?? [],\n errorMap: defaultState.errorMap ?? {},\n fieldMeta: defaultState.fieldMeta ?? ({} as never),\n canSubmit: defaultState.canSubmit ?? true,\n isFieldsValid: defaultState.isFieldsValid ?? false,\n isFieldsValidating: defaultState.isFieldsValidating ?? false,\n isFormValid: defaultState.isFormValid ?? false,\n isFormValidating: defaultState.isFormValidating ?? false,\n isSubmitted: defaultState.isSubmitted ?? false,\n isSubmitting: defaultState.isSubmitting ?? false,\n isTouched: defaultState.isTouched ?? false,\n isValid: defaultState.isValid ?? false,\n isValidating: defaultState.isValidating ?? false,\n submissionAttempts: defaultState.submissionAttempts ?? 0,\n formValidationCount: defaultState.formValidationCount ?? 0,\n }\n}\n\nexport class FormApi<TFormData, ValidatorType> {\n // // This carries the context for nested fields\n options: FormOptions<TFormData, ValidatorType> = {}\n store!: Store<FormState<TFormData>>\n // Do not use __state directly, as it is not reactive.\n // Please use form.useStore() utility to subscribe to state\n state!: FormState<TFormData>\n fieldInfo: Record<DeepKeys<TFormData>, FieldInfo<TFormData, ValidatorType>> =\n {} as any\n fieldName?: string\n validationMeta: ValidationMeta = {}\n\n constructor(opts?: FormOptions<TFormData, ValidatorType>) {\n this.store = new Store<FormState<TFormData>>(\n getDefaultFormState({\n ...(opts?.defaultState as any),\n values: opts?.defaultValues ?? opts?.defaultState?.values,\n isFormValid: true,\n }),\n {\n onUpdate: () => {\n let { state } = this.store\n // Computed state\n const fieldMetaValues = Object.values(state.fieldMeta) as (\n | FieldMeta\n | undefined\n )[]\n\n const isFieldsValidating = fieldMetaValues.some(\n (field) => field?.isValidating,\n )\n\n const isFieldsValid = !fieldMetaValues.some(\n (field) =>\n field?.errorMap &&\n isNonEmptyArray(Object.values(field.errorMap).filter(Boolean)),\n )\n\n const isTouched = fieldMetaValues.some((field) => field?.isTouched)\n\n const isValidating = isFieldsValidating || state.isFormValidating\n state.errors = Object.values(state.errorMap).filter(\n (val: unknown) => val !== undefined,\n )\n const isFormValid = state.errors.length === 0\n const isValid = isFieldsValid && isFormValid\n const canSubmit =\n (state.submissionAttempts === 0 && !isTouched) ||\n (!isValidating && !state.isSubmitting && isValid)\n\n state = {\n ...state,\n isFieldsValidating,\n isFieldsValid,\n isFormValid,\n isValid,\n canSubmit,\n isTouched,\n }\n\n this.store.state = state\n this.state = state\n },\n },\n )\n\n this.state = this.store.state\n\n this.update(opts || {})\n }\n\n mount = () => {\n const doValidate = () => {\n if (typeof this.options.onMount === 'function') {\n return this.options.onMount(this.state.values, this)\n }\n if (this.options.validator) {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n this.options.onMount,\n )\n }\n }\n const error = doValidate()\n if (error) {\n this.store.setState((prev) => ({\n ...prev,\n errorMap: { ...prev.errorMap, onMount: error },\n }))\n }\n }\n\n update = (options?: FormOptions<TFormData, ValidatorType>) => {\n if (!options) return\n\n this.store.batch(() => {\n const shouldUpdateValues =\n options.defaultValues &&\n options.defaultValues !== this.options.defaultValues &&\n !this.state.isTouched\n\n const shouldUpdateState =\n options.defaultState !== this.options.defaultState &&\n !this.state.isTouched\n\n this.store.setState(() =>\n getDefaultFormState(\n Object.assign(\n {},\n this.state as any,\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateState ? options.defaultState : {},\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n shouldUpdateValues\n ? {\n values: options.defaultValues,\n }\n : {},\n ),\n ),\n )\n })\n\n this.options = options\n }\n\n reset = () =>\n this.store.setState(() =>\n getDefaultFormState({\n ...(this.options.defaultState as any),\n values: this.options.defaultValues ?? this.options.defaultState?.values,\n }),\n )\n\n validateAllFields = async (cause: ValidationCause) => {\n const fieldValidationPromises: Promise<ValidationError[]>[] = [] as any\n this.store.batch(() => {\n void (\n Object.values(this.fieldInfo) as FieldInfo<any, ValidatorType>[]\n ).forEach((field) => {\n Object.values(field.instances).forEach((instance) => {\n // If any fields are not touched\n if (!instance.state.meta.isTouched) {\n // Mark them as touched\n instance.setMeta((prev) => ({ ...prev, isTouched: true }))\n // Validate the field\n fieldValidationPromises.push(\n Promise.resolve().then(() => instance.validate(cause)),\n )\n }\n })\n })\n })\n\n return Promise.all(fieldValidationPromises)\n }\n\n validateSync = (cause: ValidationCause): void => {\n const { onChange, onBlur } = this.options\n const validate =\n cause === 'change' ? onChange : cause === 'blur' ? onBlur : undefined\n if (!validate) return\n\n const errorMapKey = getErrorMapKey(cause)\n const doValidate = () => {\n if (typeof validate === 'function') {\n return validate(this.state.values, this) as ValidationError\n }\n if (this.options.validator && typeof validate !== 'function') {\n return (this.options.validator as Validator<TFormData>)().validate(\n this.state.values,\n validate,\n )\n }\n throw new Error(\n `Form validation for ${errorMapKey} failed. ${errorMapKey} should either be a function, or \\`validator\\` should be correct.`,\n )\n }\n\n const error = normalizeError(doValidate())\n if (this.state.errorMap[errorMapKey] !== error) {\n this.store.setState((prev) => ({\n ...prev,\n errorMap: {\n ...prev.errorMap,\n [errorMapKey]: error,\n },\n }))\n }\n\n if (this.state.errorMap[errorMapKey]) {\n this.cancelValidateAsync()\n }\n }\n\n __leaseValidateAsync = () => {\n const count = (this.validationMeta.validationAsyncCount || 0) + 1\n this.validationMeta.validationAsyncCount = count\n return count\n }\n\n cancelValidateAsync = () => {\n // Lease a new validation count to ignore any pending validations\n this.__leaseValidateAsync()\n // Cancel any pending validation state\n this.store.setState((prev) => ({\n ...prev,\n isFormValidating: false,\n }))\n }\n\n validateAsync = async (\n cause: ValidationCause,\n ): Promise<ValidationError[]> => {\n const {\n onChangeAsync,\n onBlurAsync,\n asyncDebounceMs,\n onBlurAsyncDebounceMs,\n onChangeAsyncDebounceMs,\n } = this.options\n\n const validate =\n cause === 'change'\n ? onChangeAsync\n : cause === 'blur'\n ? onBlurAsync\n : undefined\n\n if (!validate) return []\n const debounceMs =\n (cause === 'change' ? onChangeAsyncDebounceMs : onBlurAsyncDebounceMs) ??\n asyncDebounceMs ??\n 0\n\n if (!this.state.isFormValidating) {\n this.store.setState((prev) => ({ ...prev, isFormValidating: true }))\n }\n\n // Use the validationCount for all field instances to\n // track freshness of the validation\n const validationAsyncCount = this.__leaseValidateAsync()\n\n const checkLatest = () =>\n validationAsyncCount === this.validationMeta.validationAsyncCount\n\n if (!this.validationMeta.validationPromise) {\n this.validationMeta.validationPromise = new Promise((resolve, reject) => {\n this.validationMeta.validationResolve = resolve\n this.validationMeta.validationReject = reject\n })\n }\n\n if (debounceMs > 0) {\n await new Promise((r) => setTimeout(r, debounceMs))\n }\n\n const doValidate = () => {\n if (typeof validate === 'function') {\n return validate(this.state.values, this) as ValidationError\n }\n if (this.options.validator && typeof validate !== 'function') {\n return (this.options.validator as Validator<TFormData>)().validateAsync(\n this.state.values,\n validate,\n )\n }\n const errorMapKey = getErrorMapKey(cause)\n throw new Error(\n `Form validation for ${errorMapKey}Async failed. ${errorMapKey}Async should either be a function, or \\`validator\\` should be correct.`,\n )\n }\n\n // Only kick off validation if this validation is the latest attempt\n if (checkLatest()) {\n const prevErrors = this.state.errors\n try {\n const rawError = await doValidate()\n if (checkLatest()) {\n const error = normalizeError(rawError)\n this.store.setState((prev) => ({\n ...prev,\n isFormValidating: false,\n errorMap: {\n ...prev.errorMap,\n [getErrorMapKey(cause)]: error,\n },\n }))\n this.validationMeta.validationResolve?.([...prevErrors, error])\n }\n } catch (error) {\n if (checkLatest()) {\n this.validationMeta.validationReject?.([...prevErrors, error])\n throw error\n }\n } finally {\n if (checkLatest()) {\n this.store.setState((prev) => ({ ...prev, isFormValidating: false }))\n delete this.validationMeta.validationPromise\n }\n }\n }\n // Always return the latest validation promise to the caller\n return (await this.validationMeta.validationPromise) ?? []\n }\n\n validate = (\n cause: ValidationCause,\n ): ValidationError[] | Promise<ValidationError[]> => {\n // Store the previous error for the errorMapKey (eg. onChange, onBlur, onSubmit)\n const errorMapKey = getErrorMapKey(cause)\n const prevError = this.state.errorMap[errorMapKey]\n\n // Attempt to sync validate first\n this.validateSync(cause)\n\n const newError = this.state.errorMap[errorMapKey]\n if (\n prevError !== newError &&\n !this.options.asyncAlways &&\n !(newError === undefined && prevError !== undefined)\n )\n return this.state.errors\n\n // No error? Attempt async validation\n return this.validateAsync(cause)\n }\n\n handleSubmit = async () => {\n // Check to see that the form and all fields have been touched\n // If they have not, touch them all and run validation\n // Run form validation\n // Submit the form\n\n this.store.setState((old) => ({\n ...old,\n // Submission attempts mark the form as not submitted\n isSubmitted: false,\n // Count submission attempts\n submissionAttempts: old.submissionAttempts + 1,\n }))\n\n // Don't let invalid forms submit\n if (!this.state.canSubmit) return\n\n this.store.setState((d) => ({ ...d, isSubmitting: true }))\n\n const done = () => {\n this.store.setState((prev) => ({ ...prev, isSubmitting: false }))\n }\n\n // Validate all fields\n await this.validateAllFields('submit')\n\n // Fields are invalid, do not submit\n if (!this.state.isFieldsValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n // Run validation for the form\n await this.validate('submit')\n\n if (!this.state.isValid) {\n done()\n this.options.onSubmitInvalid?.(this.state.values, this)\n return\n }\n\n try {\n // Run the submit code\n await this.options.onSubmit?.(this.state.values, this)\n\n this.store.batch(() => {\n this.store.setState((prev) => ({ ...prev, isSubmitted: true }))\n done()\n })\n } catch (err) {\n done()\n throw err\n }\n }\n\n getFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): DeepValue<TFormData, TField> => getBy(this.state.values, field)\n\n getFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldMeta | undefined => {\n return this.state.fieldMeta[field]\n }\n\n getFieldInfo = <TField extends DeepKeys<TFormData>>(\n field: TField,\n ): FieldInfo<TFormData, ValidatorType> => {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n return (this.fieldInfo[field] ||= {\n instances: {},\n })\n }\n\n setFieldMeta = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<FieldMeta>,\n ) => {\n this.store.setState((prev) => {\n return {\n ...prev,\n fieldMeta: {\n ...prev.fieldMeta,\n [field]: functionalUpdate(updater, prev.fieldMeta[field]),\n },\n }\n })\n }\n\n setFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n updater: Updater<DeepValue<TFormData, TField>>,\n opts?: { touch?: boolean },\n ) => {\n const touch = opts?.touch\n\n this.store.batch(() => {\n if (touch) {\n this.setFieldMeta(field, (prev) => ({\n ...prev,\n isTouched: true,\n }))\n }\n\n this.store.setState((prev) => {\n return {\n ...prev,\n values: setBy(prev.values, field, updater),\n }\n })\n })\n }\n\n deleteField = <TField extends DeepKeys<TFormData>>(field: TField) => {\n const newState = { ...this.state }\n delete newState.values[field as keyof TFormData]\n delete newState.fieldMeta[field]\n\n this.store.setState((_) => newState)\n }\n\n pushFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n return this.setFieldValue(\n field,\n (prev) => [...(Array.isArray(prev) ? prev : []), value] as any,\n opts,\n )\n }\n\n insertFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n value: DeepValue<TFormData, TField> extends any[]\n ? DeepValue<TFormData, TField>[number]\n : never,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).map((d, i) =>\n i === index ? value : d,\n ) as any\n },\n opts,\n )\n }\n\n removeFieldValue = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index: number,\n opts?: { touch?: boolean },\n ) => {\n this.setFieldValue(\n field,\n (prev) => {\n return (prev as DeepValue<TFormData, TField>[]).filter(\n (_d, i) => i !== index,\n ) as any\n },\n opts,\n )\n }\n\n swapFieldValues = <TField extends DeepKeys<TFormData>>(\n field: TField,\n index1: number,\n index2: number,\n ) => {\n this.setFieldValue(field, (prev: any) => {\n const prev1 = prev[index1]!\n const prev2 = prev[index2]!\n return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)\n })\n }\n}\n\nfunction normalizeError(rawError?: ValidationError) {\n if (rawError) {\n if (typeof rawError !== 'string') {\n return 'Invalid Form Values'\n }\n\n return rawError\n }\n\n return undefined\n}\n\nfunction getErrorMapKey(cause: ValidationCause) {\n switch (cause) {\n case 'submit':\n return 'onSubmit'\n case 'change':\n return 'onChange'\n case 'blur':\n return 'onBlur'\n case 'mount':\n return 'onMount'\n }\n}\n"],"mappings":"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","names":[]}
@@ -9,6 +9,7 @@ type ValidateAsyncFn$1<TData, ValidatorType> = (value: TData, fieldApi: FormApi<
9
9
  type FormOptions<TData, ValidatorType> = {
10
10
  defaultValues?: TData;
11
11
  defaultState?: Partial<FormState<TData>>;
12
+ asyncAlways?: boolean;
12
13
  asyncDebounceMs?: number;
13
14
  validator?: ValidatorType;
14
15
  onMount?: ValidateOrFn$1<TData, ValidatorType>;
@@ -27,8 +28,8 @@ type FieldInfo<TFormData, ValidatorType> = {
27
28
  type ValidationMeta = {
28
29
  validationCount?: number;
29
30
  validationAsyncCount?: number;
30
- validationPromise?: Promise<ValidationError[]>;
31
- validationResolve?: (errors: ValidationError[]) => void;
31
+ validationPromise?: Promise<ValidationError[] | undefined>;
32
+ validationResolve?: (errors: ValidationError[] | undefined) => void;
32
33
  validationReject?: (errors: unknown) => void;
33
34
  };
34
35
  type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`;
@@ -40,7 +41,8 @@ type FormState<TData> = {
40
41
  isFormValidating: boolean;
41
42
  formValidationCount: number;
42
43
  isFormValid: boolean;
43
- formError?: ValidationError;
44
+ errors: ValidationError[];
45
+ errorMap: ValidationErrorMap;
44
46
  fieldMeta: Record<DeepKeys<TData>, FieldMeta>;
45
47
  isFieldsValidating: boolean;
46
48
  isFieldsValid: boolean;
@@ -60,10 +62,15 @@ declare class FormApi<TFormData, ValidatorType> {
60
62
  fieldName?: string;
61
63
  validationMeta: ValidationMeta;
62
64
  constructor(opts?: FormOptions<TFormData, ValidatorType>);
63
- mount: () => any;
65
+ mount: () => void;
64
66
  update: (options?: FormOptions<TFormData, ValidatorType>) => void;
65
67
  reset: () => void;
66
68
  validateAllFields: (cause: ValidationCause) => Promise<ValidationError[][]>;
69
+ validateSync: (cause: ValidationCause) => void;
70
+ __leaseValidateAsync: () => number;
71
+ cancelValidateAsync: () => void;
72
+ validateAsync: (cause: ValidationCause) => Promise<ValidationError[]>;
73
+ validate: (cause: ValidationCause) => ValidationError[] | Promise<ValidationError[]>;
67
74
  handleSubmit: () => Promise<void>;
68
75
  getFieldValue: <TField extends DeepKeys<TFormData>>(field: TField) => DeepValue<TFormData, TField>;
69
76
  getFieldMeta: <TField extends DeepKeys<TFormData>>(field: TField) => FieldMeta | undefined;
@@ -9,6 +9,7 @@ type ValidateAsyncFn$1<TData, ValidatorType> = (value: TData, fieldApi: FormApi<
9
9
  type FormOptions<TData, ValidatorType> = {
10
10
  defaultValues?: TData;
11
11
  defaultState?: Partial<FormState<TData>>;
12
+ asyncAlways?: boolean;
12
13
  asyncDebounceMs?: number;
13
14
  validator?: ValidatorType;
14
15
  onMount?: ValidateOrFn$1<TData, ValidatorType>;
@@ -27,8 +28,8 @@ type FieldInfo<TFormData, ValidatorType> = {
27
28
  type ValidationMeta = {
28
29
  validationCount?: number;
29
30
  validationAsyncCount?: number;
30
- validationPromise?: Promise<ValidationError[]>;
31
- validationResolve?: (errors: ValidationError[]) => void;
31
+ validationPromise?: Promise<ValidationError[] | undefined>;
32
+ validationResolve?: (errors: ValidationError[] | undefined) => void;
32
33
  validationReject?: (errors: unknown) => void;
33
34
  };
34
35
  type ValidationErrorMapKeys = `on${Capitalize<ValidationCause>}`;
@@ -40,7 +41,8 @@ type FormState<TData> = {
40
41
  isFormValidating: boolean;
41
42
  formValidationCount: number;
42
43
  isFormValid: boolean;
43
- formError?: ValidationError;
44
+ errors: ValidationError[];
45
+ errorMap: ValidationErrorMap;
44
46
  fieldMeta: Record<DeepKeys<TData>, FieldMeta>;
45
47
  isFieldsValidating: boolean;
46
48
  isFieldsValid: boolean;
@@ -60,10 +62,15 @@ declare class FormApi<TFormData, ValidatorType> {
60
62
  fieldName?: string;
61
63
  validationMeta: ValidationMeta;
62
64
  constructor(opts?: FormOptions<TFormData, ValidatorType>);
63
- mount: () => any;
65
+ mount: () => void;
64
66
  update: (options?: FormOptions<TFormData, ValidatorType>) => void;
65
67
  reset: () => void;
66
68
  validateAllFields: (cause: ValidationCause) => Promise<ValidationError[][]>;
69
+ validateSync: (cause: ValidationCause) => void;
70
+ __leaseValidateAsync: () => number;
71
+ cancelValidateAsync: () => void;
72
+ validateAsync: (cause: ValidationCause) => Promise<ValidationError[]>;
73
+ validate: (cause: ValidationCause) => ValidationError[] | Promise<ValidationError[]>;
67
74
  handleSubmit: () => Promise<void>;
68
75
  getFieldValue: <TField extends DeepKeys<TFormData>>(field: TField) => DeepValue<TFormData, TField>;
69
76
  getFieldMeta: <TField extends DeepKeys<TFormData>>(field: TField) => FieldMeta | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/form-core",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Powerful, type-safe, framework agnostic forms.",
5
5
  "author": "tannerlinsley",
6
6
  "license": "MIT",
package/src/FieldApi.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type DeepKeys, type DeepValue, type Updater } from './utils'
2
- import type { FormApi, ValidationErrorMap } from './FormApi'
3
1
  import { Store } from '@tanstack/store'
4
- import type { Validator, ValidationError } from './types'
2
+ import type { FormApi, ValidationErrorMap } from './FormApi'
3
+ import type { ValidationError, Validator } from './types'
4
+ import type { DeepKeys, DeepValue, Updater } from './utils'
5
5
 
6
6
  export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount'
7
7
 
@@ -495,7 +495,7 @@ export class FieldApi<
495
495
  }
496
496
 
497
497
  // Always return the latest validation promise to the caller
498
- return this.getInfo().validationPromise ?? []
498
+ return (await this.getInfo().validationPromise) ?? []
499
499
  }
500
500
 
501
501
  validate = (
@@ -505,6 +505,10 @@ export class FieldApi<
505
505
  // If the field is pristine and validatePristine is false, do not validate
506
506
  if (!this.state.meta.isTouched) return []
507
507
 
508
+ try {
509
+ this.form.validate(cause)
510
+ } catch (_) {}
511
+
508
512
  // Store the previous error for the errorMapKey (eg. onChange, onBlur, onSubmit)
509
513
  const errorMapKey = getErrorMapKey(cause)
510
514
  const prevError = this.getMeta().errorMap[errorMapKey]