New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@tanstack/form-core

Package Overview
Dependencies
Maintainers
2
Versions
129
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tanstack/form-core - npm Package Compare versions

Comparing version 0.7.2 to 0.8.0

2

build/legacy/FieldApi.d.ts
export { FieldApi, FieldApiOptions, FieldMeta, FieldOptions, FieldState, ResolveName, ValidationCause } from './index.js';
import './utils.js';
import '@tanstack/store';
import './types.js';
import './utils.js';

@@ -201,3 +201,3 @@ // src/FieldApi.ts

}
return this.getInfo().validationPromise ?? [];
return await this.getInfo().validationPromise ?? [];
};

@@ -207,2 +207,6 @@ this.validate = (cause, value) => {

return [];
try {
this.form.validate(cause);
} catch (_) {
}
const errorMapKey = getErrorMapKey(cause);

@@ -209,0 +213,0 @@ const prevError = this.getMeta().errorMap[errorMapKey];

@@ -7,2 +7,4 @@ // src/FormApi.ts

values: defaultState.values ?? {},
errors: defaultState.errors ?? [],
errorMap: defaultState.errorMap ?? {},
fieldMeta: defaultState.fieldMeta ?? {},

@@ -30,11 +32,20 @@ canSubmit: defaultState.canSubmit ?? true,

this.mount = () => {
if (typeof this.options.onMount === "function") {
return this.options.onMount(this.state.values, this);
const doValidate = () => {
if (typeof this.options.onMount === "function") {
return this.options.onMount(this.state.values, this);
}
if (this.options.validator) {
return this.options.validator().validate(
this.state.values,
this.options.onMount
);
}
};
const error = doValidate();
if (error) {
this.store.setState((prev) => ({
...prev,
errorMap: { ...prev.errorMap, onMount: error }
}));
}
if (this.options.validator) {
return this.options.validator().validate(
this.state.values,
this.options.onMount
);
}
};

@@ -89,2 +100,129 @@ this.update = (options) => {

};
this.validateSync = (cause) => {
const { onChange, onBlur } = this.options;
const validate = cause === "change" ? onChange : cause === "blur" ? onBlur : void 0;
if (!validate)
return;
const errorMapKey = getErrorMapKey(cause);
const doValidate = () => {
if (typeof validate === "function") {
return validate(this.state.values, this);
}
if (this.options.validator && typeof validate !== "function") {
return this.options.validator().validate(
this.state.values,
validate
);
}
throw new Error(
`Form validation for ${errorMapKey} failed. ${errorMapKey} should either be a function, or \`validator\` should be correct.`
);
};
const error = normalizeError(doValidate());
if (this.state.errorMap[errorMapKey] !== error) {
this.store.setState((prev) => ({
...prev,
errorMap: {
...prev.errorMap,
[errorMapKey]: error
}
}));
}
if (this.state.errorMap[errorMapKey]) {
this.cancelValidateAsync();
}
};
this.__leaseValidateAsync = () => {
const count = (this.validationMeta.validationAsyncCount || 0) + 1;
this.validationMeta.validationAsyncCount = count;
return count;
};
this.cancelValidateAsync = () => {
this.__leaseValidateAsync();
this.store.setState((prev) => ({
...prev,
isFormValidating: false
}));
};
this.validateAsync = async (cause) => {
var _a, _b, _c, _d;
const {
onChangeAsync,
onBlurAsync,
asyncDebounceMs,
onBlurAsyncDebounceMs,
onChangeAsyncDebounceMs
} = this.options;
const validate = cause === "change" ? onChangeAsync : cause === "blur" ? onBlurAsync : void 0;
if (!validate)
return [];
const debounceMs = (cause === "change" ? onChangeAsyncDebounceMs : onBlurAsyncDebounceMs) ?? asyncDebounceMs ?? 0;
if (!this.state.isFormValidating) {
this.store.setState((prev) => ({ ...prev, isFormValidating: true }));
}
const validationAsyncCount = this.__leaseValidateAsync();
const checkLatest = () => validationAsyncCount === this.validationMeta.validationAsyncCount;
if (!this.validationMeta.validationPromise) {
this.validationMeta.validationPromise = new Promise((resolve, reject) => {
this.validationMeta.validationResolve = resolve;
this.validationMeta.validationReject = reject;
});
}
if (debounceMs > 0) {
await new Promise((r) => setTimeout(r, debounceMs));
}
const doValidate = () => {
if (typeof validate === "function") {
return validate(this.state.values, this);
}
if (this.options.validator && typeof validate !== "function") {
return this.options.validator().validateAsync(
this.state.values,
validate
);
}
const errorMapKey = getErrorMapKey(cause);
throw new Error(
`Form validation for ${errorMapKey}Async failed. ${errorMapKey}Async should either be a function, or \`validator\` should be correct.`
);
};
if (checkLatest()) {
const prevErrors = this.state.errors;
try {
const rawError = await doValidate();
if (checkLatest()) {
const error = normalizeError(rawError);
this.store.setState((prev) => ({
...prev,
isFormValidating: false,
errorMap: {
...prev.errorMap,
[getErrorMapKey(cause)]: error
}
}));
(_b = (_a = this.validationMeta).validationResolve) == null ? void 0 : _b.call(_a, [...prevErrors, error]);
}
} catch (error) {
if (checkLatest()) {
(_d = (_c = this.validationMeta).validationReject) == null ? void 0 : _d.call(_c, [...prevErrors, error]);
throw error;
}
} finally {
if (checkLatest()) {
this.store.setState((prev) => ({ ...prev, isFormValidating: false }));
delete this.validationMeta.validationPromise;
}
}
}
return await this.validationMeta.validationPromise ?? [];
};
this.validate = (cause) => {
const errorMapKey = getErrorMapKey(cause);
const prevError = this.state.errorMap[errorMapKey];
this.validateSync(cause);
const newError = this.state.errorMap[errorMapKey];
if (prevError !== newError && !this.options.asyncAlways && !(newError === void 0 && prevError !== void 0))
return this.state.errors;
return this.validateAsync(cause);
};
this.handleSubmit = async () => {

@@ -111,2 +249,3 @@ var _a, _b, _c, _d, _e, _f;

}
await this.validate("submit");
if (!this.state.isValid) {

@@ -227,3 +366,6 @@ done();

const isValidating = isFieldsValidating || state.isFormValidating;
const isFormValid = !state.formError;
state.errors = Object.values(state.errorMap).filter(
(val) => val !== void 0
);
const isFormValid = state.errors.length === 0;
const isValid = isFieldsValid && isFormValid;

@@ -249,2 +391,23 @@ const canSubmit = state.submissionAttempts === 0 && !isTouched || !isValidating && !state.isSubmitting && isValid;

};
function normalizeError(rawError) {
if (rawError) {
if (typeof rawError !== "string") {
return "Invalid Form Values";
}
return rawError;
}
return void 0;
}
function getErrorMapKey(cause) {
switch (cause) {
case "submit":
return "onSubmit";
case "change":
return "onChange";
case "blur":
return "onBlur";
case "mount":
return "onMount";
}
}
export {

@@ -251,0 +414,0 @@ FormApi

@@ -12,2 +12,3 @@ import { Store } from '@tanstack/store';

defaultState?: Partial<FormState<TData>>;
asyncAlways?: boolean;
asyncDebounceMs?: number;

@@ -31,4 +32,4 @@ validator?: ValidatorType;

validationAsyncCount?: number;
validationPromise?: Promise<ValidationError[]>;
validationResolve?: (errors: ValidationError[]) => void;
validationPromise?: Promise<ValidationError[] | undefined>;
validationResolve?: (errors: ValidationError[] | undefined) => void;
validationReject?: (errors: unknown) => void;

@@ -45,3 +46,4 @@ };

isFormValid: boolean;
formError?: ValidationError;
errors: ValidationError[];
errorMap: ValidationErrorMap;
fieldMeta: Record<DeepKeys<TData>, FieldMeta>;

@@ -66,6 +68,11 @@ isFieldsValidating: boolean;

constructor(opts?: FormOptions<TFormData, ValidatorType>);
mount: () => any;
mount: () => void;
update: (options?: FormOptions<TFormData, ValidatorType>) => void;
reset: () => void;
validateAllFields: (cause: ValidationCause) => Promise<ValidationError[][]>;
validateSync: (cause: ValidationCause) => void;
__leaseValidateAsync: () => number;
cancelValidateAsync: () => void;
validateAsync: (cause: ValidationCause) => Promise<ValidationError[]>;
validate: (cause: ValidationCause) => ValidationError[] | Promise<ValidationError[]>;
handleSubmit: () => Promise<void>;

@@ -72,0 +79,0 @@ getFieldValue: <TField extends DeepKeys<TFormData>>(field: TField) => DeepValue<TFormData, TField>;

export { FieldApi, FieldApiOptions, FieldMeta, FieldOptions, FieldState, ResolveName, ValidationCause } from './index.js';
import './utils.js';
import '@tanstack/store';
import './types.js';
import './utils.js';

@@ -198,3 +198,3 @@ // src/FieldApi.ts

}
return this.getInfo().validationPromise ?? [];
return await this.getInfo().validationPromise ?? [];
};

@@ -204,2 +204,6 @@ this.validate = (cause, value) => {

return [];
try {
this.form.validate(cause);
} catch (_) {
}
const errorMapKey = getErrorMapKey(cause);

@@ -206,0 +210,0 @@ const prevError = this.getMeta().errorMap[errorMapKey];

@@ -7,2 +7,4 @@ // src/FormApi.ts

values: defaultState.values ?? {},
errors: defaultState.errors ?? [],
errorMap: defaultState.errorMap ?? {},
fieldMeta: defaultState.fieldMeta ?? {},

@@ -30,11 +32,20 @@ canSubmit: defaultState.canSubmit ?? true,

this.mount = () => {
if (typeof this.options.onMount === "function") {
return this.options.onMount(this.state.values, this);
const doValidate = () => {
if (typeof this.options.onMount === "function") {
return this.options.onMount(this.state.values, this);
}
if (this.options.validator) {
return this.options.validator().validate(
this.state.values,
this.options.onMount
);
}
};
const error = doValidate();
if (error) {
this.store.setState((prev) => ({
...prev,
errorMap: { ...prev.errorMap, onMount: error }
}));
}
if (this.options.validator) {
return this.options.validator().validate(
this.state.values,
this.options.onMount
);
}
};

@@ -86,2 +97,128 @@ this.update = (options) => {

};
this.validateSync = (cause) => {
const { onChange, onBlur } = this.options;
const validate = cause === "change" ? onChange : cause === "blur" ? onBlur : void 0;
if (!validate)
return;
const errorMapKey = getErrorMapKey(cause);
const doValidate = () => {
if (typeof validate === "function") {
return validate(this.state.values, this);
}
if (this.options.validator && typeof validate !== "function") {
return this.options.validator().validate(
this.state.values,
validate
);
}
throw new Error(
`Form validation for ${errorMapKey} failed. ${errorMapKey} should either be a function, or \`validator\` should be correct.`
);
};
const error = normalizeError(doValidate());
if (this.state.errorMap[errorMapKey] !== error) {
this.store.setState((prev) => ({
...prev,
errorMap: {
...prev.errorMap,
[errorMapKey]: error
}
}));
}
if (this.state.errorMap[errorMapKey]) {
this.cancelValidateAsync();
}
};
this.__leaseValidateAsync = () => {
const count = (this.validationMeta.validationAsyncCount || 0) + 1;
this.validationMeta.validationAsyncCount = count;
return count;
};
this.cancelValidateAsync = () => {
this.__leaseValidateAsync();
this.store.setState((prev) => ({
...prev,
isFormValidating: false
}));
};
this.validateAsync = async (cause) => {
const {
onChangeAsync,
onBlurAsync,
asyncDebounceMs,
onBlurAsyncDebounceMs,
onChangeAsyncDebounceMs
} = this.options;
const validate = cause === "change" ? onChangeAsync : cause === "blur" ? onBlurAsync : void 0;
if (!validate)
return [];
const debounceMs = (cause === "change" ? onChangeAsyncDebounceMs : onBlurAsyncDebounceMs) ?? asyncDebounceMs ?? 0;
if (!this.state.isFormValidating) {
this.store.setState((prev) => ({ ...prev, isFormValidating: true }));
}
const validationAsyncCount = this.__leaseValidateAsync();
const checkLatest = () => validationAsyncCount === this.validationMeta.validationAsyncCount;
if (!this.validationMeta.validationPromise) {
this.validationMeta.validationPromise = new Promise((resolve, reject) => {
this.validationMeta.validationResolve = resolve;
this.validationMeta.validationReject = reject;
});
}
if (debounceMs > 0) {
await new Promise((r) => setTimeout(r, debounceMs));
}
const doValidate = () => {
if (typeof validate === "function") {
return validate(this.state.values, this);
}
if (this.options.validator && typeof validate !== "function") {
return this.options.validator().validateAsync(
this.state.values,
validate
);
}
const errorMapKey = getErrorMapKey(cause);
throw new Error(
`Form validation for ${errorMapKey}Async failed. ${errorMapKey}Async should either be a function, or \`validator\` should be correct.`
);
};
if (checkLatest()) {
const prevErrors = this.state.errors;
try {
const rawError = await doValidate();
if (checkLatest()) {
const error = normalizeError(rawError);
this.store.setState((prev) => ({
...prev,
isFormValidating: false,
errorMap: {
...prev.errorMap,
[getErrorMapKey(cause)]: error
}
}));
this.validationMeta.validationResolve?.([...prevErrors, error]);
}
} catch (error) {
if (checkLatest()) {
this.validationMeta.validationReject?.([...prevErrors, error]);
throw error;
}
} finally {
if (checkLatest()) {
this.store.setState((prev) => ({ ...prev, isFormValidating: false }));
delete this.validationMeta.validationPromise;
}
}
}
return await this.validationMeta.validationPromise ?? [];
};
this.validate = (cause) => {
const errorMapKey = getErrorMapKey(cause);
const prevError = this.state.errorMap[errorMapKey];
this.validateSync(cause);
const newError = this.state.errorMap[errorMapKey];
if (prevError !== newError && !this.options.asyncAlways && !(newError === void 0 && prevError !== void 0))
return this.state.errors;
return this.validateAsync(cause);
};
this.handleSubmit = async () => {

@@ -107,2 +244,3 @@ this.store.setState((old) => ({

}
await this.validate("submit");
if (!this.state.isValid) {

@@ -221,3 +359,6 @@ done();

const isValidating = isFieldsValidating || state.isFormValidating;
const isFormValid = !state.formError;
state.errors = Object.values(state.errorMap).filter(
(val) => val !== void 0
);
const isFormValid = state.errors.length === 0;
const isValid = isFieldsValid && isFormValid;

@@ -243,2 +384,23 @@ const canSubmit = state.submissionAttempts === 0 && !isTouched || !isValidating && !state.isSubmitting && isValid;

};
function normalizeError(rawError) {
if (rawError) {
if (typeof rawError !== "string") {
return "Invalid Form Values";
}
return rawError;
}
return void 0;
}
function getErrorMapKey(cause) {
switch (cause) {
case "submit":
return "onSubmit";
case "change":
return "onChange";
case "blur":
return "onBlur";
case "mount":
return "onMount";
}
}
export {

@@ -245,0 +407,0 @@ FormApi

@@ -12,2 +12,3 @@ import { Store } from '@tanstack/store';

defaultState?: Partial<FormState<TData>>;
asyncAlways?: boolean;
asyncDebounceMs?: number;

@@ -31,4 +32,4 @@ validator?: ValidatorType;

validationAsyncCount?: number;
validationPromise?: Promise<ValidationError[]>;
validationResolve?: (errors: ValidationError[]) => void;
validationPromise?: Promise<ValidationError[] | undefined>;
validationResolve?: (errors: ValidationError[] | undefined) => void;
validationReject?: (errors: unknown) => void;

@@ -45,3 +46,4 @@ };

isFormValid: boolean;
formError?: ValidationError;
errors: ValidationError[];
errorMap: ValidationErrorMap;
fieldMeta: Record<DeepKeys<TData>, FieldMeta>;

@@ -66,6 +68,11 @@ isFieldsValidating: boolean;

constructor(opts?: FormOptions<TFormData, ValidatorType>);
mount: () => any;
mount: () => void;
update: (options?: FormOptions<TFormData, ValidatorType>) => void;
reset: () => void;
validateAllFields: (cause: ValidationCause) => Promise<ValidationError[][]>;
validateSync: (cause: ValidationCause) => void;
__leaseValidateAsync: () => number;
cancelValidateAsync: () => void;
validateAsync: (cause: ValidationCause) => Promise<ValidationError[]>;
validate: (cause: ValidationCause) => ValidationError[] | Promise<ValidationError[]>;
handleSubmit: () => Promise<void>;

@@ -72,0 +79,0 @@ getFieldValue: <TField extends DeepKeys<TFormData>>(field: TField) => DeepValue<TFormData, TField>;

{
"name": "@tanstack/form-core",
"version": "0.7.2",
"version": "0.8.0",
"description": "Powerful, type-safe, framework agnostic forms.",

@@ -5,0 +5,0 @@ "author": "tannerlinsley",

@@ -1,5 +0,5 @@

import { type DeepKeys, type DeepValue, type Updater } from './utils'
import { Store } from '@tanstack/store'
import type { FormApi, ValidationErrorMap } from './FormApi'
import { Store } from '@tanstack/store'
import type { Validator, ValidationError } from './types'
import type { ValidationError, Validator } from './types'
import type { DeepKeys, DeepValue, Updater } from './utils'

@@ -498,3 +498,3 @@ export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount'

// Always return the latest validation promise to the caller
return this.getInfo().validationPromise ?? []
return (await this.getInfo().validationPromise) ?? []
}

@@ -509,2 +509,6 @@

try {
this.form.validate(cause)
} catch (_) {}
// Store the previous error for the errorMapKey (eg. onChange, onBlur, onSubmit)

@@ -511,0 +515,0 @@ const errorMapKey = getErrorMapKey(cause)

@@ -24,2 +24,3 @@ import { Store } from '@tanstack/store'

defaultState?: Partial<FormState<TData>>
asyncAlways?: boolean
asyncDebounceMs?: number

@@ -51,4 +52,4 @@ validator?: ValidatorType

validationAsyncCount?: number
validationPromise?: Promise<ValidationError[]>
validationResolve?: (errors: ValidationError[]) => void
validationPromise?: Promise<ValidationError[] | undefined>
validationResolve?: (errors: ValidationError[] | undefined) => void
validationReject?: (errors: unknown) => void

@@ -69,3 +70,4 @@ }

isFormValid: boolean
formError?: ValidationError
errors: ValidationError[]
errorMap: ValidationErrorMap
// Fields

@@ -90,2 +92,4 @@ fieldMeta: Record<DeepKeys<TData>, FieldMeta>

values: defaultState.values ?? ({} as never),
errors: defaultState.errors ?? [],
errorMap: defaultState.errorMap ?? {},
fieldMeta: defaultState.fieldMeta ?? ({} as never),

@@ -148,3 +152,6 @@ canSubmit: defaultState.canSubmit ?? true,

const isValidating = isFieldsValidating || state.isFormValidating
const isFormValid = !state.formError
state.errors = Object.values(state.errorMap).filter(
(val: unknown) => val !== undefined,
)
const isFormValid = state.errors.length === 0
const isValid = isFieldsValid && isFormValid

@@ -177,10 +184,19 @@ const canSubmit =

mount = () => {
if (typeof this.options.onMount === 'function') {
return this.options.onMount(this.state.values, this)
const doValidate = () => {
if (typeof this.options.onMount === 'function') {
return this.options.onMount(this.state.values, this)
}
if (this.options.validator) {
return (this.options.validator as Validator<TFormData>)().validate(
this.state.values,
this.options.onMount,
)
}
}
if (this.options.validator) {
return (this.options.validator as Validator<TFormData>)().validate(
this.state.values,
this.options.onMount,
)
const error = doValidate()
if (error) {
this.store.setState((prev) => ({
...prev,
errorMap: { ...prev.errorMap, onMount: error },
}))
}

@@ -254,2 +270,173 @@ }

validateSync = (cause: ValidationCause): void => {
const { onChange, onBlur } = this.options
const validate =
cause === 'change' ? onChange : cause === 'blur' ? onBlur : undefined
if (!validate) return
const errorMapKey = getErrorMapKey(cause)
const doValidate = () => {
if (typeof validate === 'function') {
return validate(this.state.values, this) as ValidationError
}
if (this.options.validator && typeof validate !== 'function') {
return (this.options.validator as Validator<TFormData>)().validate(
this.state.values,
validate,
)
}
throw new Error(
`Form validation for ${errorMapKey} failed. ${errorMapKey} should either be a function, or \`validator\` should be correct.`,
)
}
const error = normalizeError(doValidate())
if (this.state.errorMap[errorMapKey] !== error) {
this.store.setState((prev) => ({
...prev,
errorMap: {
...prev.errorMap,
[errorMapKey]: error,
},
}))
}
if (this.state.errorMap[errorMapKey]) {
this.cancelValidateAsync()
}
}
__leaseValidateAsync = () => {
const count = (this.validationMeta.validationAsyncCount || 0) + 1
this.validationMeta.validationAsyncCount = count
return count
}
cancelValidateAsync = () => {
// Lease a new validation count to ignore any pending validations
this.__leaseValidateAsync()
// Cancel any pending validation state
this.store.setState((prev) => ({
...prev,
isFormValidating: false,
}))
}
validateAsync = async (
cause: ValidationCause,
): Promise<ValidationError[]> => {
const {
onChangeAsync,
onBlurAsync,
asyncDebounceMs,
onBlurAsyncDebounceMs,
onChangeAsyncDebounceMs,
} = this.options
const validate =
cause === 'change'
? onChangeAsync
: cause === 'blur'
? onBlurAsync
: undefined
if (!validate) return []
const debounceMs =
(cause === 'change' ? onChangeAsyncDebounceMs : onBlurAsyncDebounceMs) ??
asyncDebounceMs ??
0
if (!this.state.isFormValidating) {
this.store.setState((prev) => ({ ...prev, isFormValidating: true }))
}
// Use the validationCount for all field instances to
// track freshness of the validation
const validationAsyncCount = this.__leaseValidateAsync()
const checkLatest = () =>
validationAsyncCount === this.validationMeta.validationAsyncCount
if (!this.validationMeta.validationPromise) {
this.validationMeta.validationPromise = new Promise((resolve, reject) => {
this.validationMeta.validationResolve = resolve
this.validationMeta.validationReject = reject
})
}
if (debounceMs > 0) {
await new Promise((r) => setTimeout(r, debounceMs))
}
const doValidate = () => {
if (typeof validate === 'function') {
return validate(this.state.values, this) as ValidationError
}
if (this.options.validator && typeof validate !== 'function') {
return (this.options.validator as Validator<TFormData>)().validateAsync(
this.state.values,
validate,
)
}
const errorMapKey = getErrorMapKey(cause)
throw new Error(
`Form validation for ${errorMapKey}Async failed. ${errorMapKey}Async should either be a function, or \`validator\` should be correct.`,
)
}
// Only kick off validation if this validation is the latest attempt
if (checkLatest()) {
const prevErrors = this.state.errors
try {
const rawError = await doValidate()
if (checkLatest()) {
const error = normalizeError(rawError)
this.store.setState((prev) => ({
...prev,
isFormValidating: false,
errorMap: {
...prev.errorMap,
[getErrorMapKey(cause)]: error,
},
}))
this.validationMeta.validationResolve?.([...prevErrors, error])
}
} catch (error) {
if (checkLatest()) {
this.validationMeta.validationReject?.([...prevErrors, error])
throw error
}
} finally {
if (checkLatest()) {
this.store.setState((prev) => ({ ...prev, isFormValidating: false }))
delete this.validationMeta.validationPromise
}
}
}
// Always return the latest validation promise to the caller
return (await this.validationMeta.validationPromise) ?? []
}
validate = (
cause: ValidationCause,
): ValidationError[] | Promise<ValidationError[]> => {
// Store the previous error for the errorMapKey (eg. onChange, onBlur, onSubmit)
const errorMapKey = getErrorMapKey(cause)
const prevError = this.state.errorMap[errorMapKey]
// Attempt to sync validate first
this.validateSync(cause)
const newError = this.state.errorMap[errorMapKey]
if (
prevError !== newError &&
!this.options.asyncAlways &&
!(newError === undefined && prevError !== undefined)
)
return this.state.errors
// No error? Attempt async validation
return this.validateAsync(cause)
}
handleSubmit = async () => {

@@ -289,3 +476,3 @@ // Check to see that the form and all fields have been touched

// Run validation for the form
// await this.validateForm()
await this.validate('submit')

@@ -439,1 +626,26 @@ if (!this.state.isValid) {

}
function normalizeError(rawError?: ValidationError) {
if (rawError) {
if (typeof rawError !== 'string') {
return 'Invalid Form Values'
}
return rawError
}
return undefined
}
function getErrorMapKey(cause: ValidationCause) {
switch (cause) {
case 'submit':
return 'onSubmit'
case 'change':
return 'onChange'
case 'blur':
return 'onBlur'
case 'mount':
return 'onMount'
}
}

@@ -5,2 +5,3 @@ import { expect } from 'vitest'

import { FieldApi } from '../FieldApi'
import { sleep } from './utils'

@@ -20,2 +21,4 @@ describe('form api', () => {

isSubmitted: false,
errors: [],
errorMap: {},
isSubmitting: false,

@@ -44,2 +47,4 @@ isTouched: false,

isFieldsValid: true,
errors: [],
errorMap: {},
isFieldsValidating: false,

@@ -68,2 +73,4 @@ isFormValid: true,

fieldMeta: {},
errors: [],
errorMap: {},
canSubmit: true,

@@ -104,2 +111,4 @@ isFieldsValid: true,

},
errors: [],
errorMap: {},
fieldMeta: {},

@@ -137,2 +146,4 @@ canSubmit: true,

},
errors: [],
errorMap: {},
fieldMeta: {},

@@ -325,2 +336,343 @@ canSubmit: true,

})
it('should run validation onChange', () => {
const form = new FormApi({
defaultValues: {
name: 'test',
},
onChange: (value) => {
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
expect(form.state.errors.length).toBe(0)
field.setValue('other', { touch: true })
expect(form.state.errors).toContain('Please enter a different value')
expect(form.state.errorMap).toMatchObject({
onChange: 'Please enter a different value',
})
})
it('should run async validation onChange', async () => {
vi.useFakeTimers()
const form = new FormApi({
defaultValues: {
name: 'test',
},
onChangeAsync: async (value) => {
await sleep(1000)
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
expect(form.state.errors.length).toBe(0)
field.setValue('other', { touch: true })
await vi.runAllTimersAsync()
expect(form.state.errors).toContain('Please enter a different value')
expect(form.state.errorMap).toMatchObject({
onChange: 'Please enter a different value',
})
})
it('should run async validation onChange with debounce', async () => {
vi.useFakeTimers()
const sleepMock = vi.fn().mockImplementation(sleep)
const form = new FormApi({
defaultValues: {
name: 'test',
},
onChangeAsyncDebounceMs: 1000,
onChangeAsync: async (value) => {
await sleepMock(1000)
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
expect(form.state.errors.length).toBe(0)
field.setValue('other', { touch: true })
field.setValue('other')
await vi.runAllTimersAsync()
// sleepMock will have been called 2 times without onChangeAsyncDebounceMs
expect(sleepMock).toHaveBeenCalledTimes(1)
expect(form.state.errors).toContain('Please enter a different value')
expect(form.state.errorMap).toMatchObject({
onChange: 'Please enter a different value',
})
})
it('should run async validation onChange with asyncDebounceMs', async () => {
vi.useFakeTimers()
const sleepMock = vi.fn().mockImplementation(sleep)
const form = new FormApi({
defaultValues: {
name: 'test',
},
asyncDebounceMs: 1000,
onChangeAsync: async (value) => {
await sleepMock(1000)
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
expect(form.state.errors.length).toBe(0)
field.setValue('other', { touch: true })
field.setValue('other')
await vi.runAllTimersAsync()
// sleepMock will have been called 2 times without asyncDebounceMs
expect(sleepMock).toHaveBeenCalledTimes(1)
expect(form.state.errors).toContain('Please enter a different value')
expect(form.state.errorMap).toMatchObject({
onChange: 'Please enter a different value',
})
})
it('should run validation onBlur', () => {
const form = new FormApi({
defaultValues: {
name: 'other',
},
onBlur: (value) => {
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
field.setValue('other', { touch: true })
field.validate('blur')
expect(form.state.errors).toContain('Please enter a different value')
expect(form.state.errorMap).toMatchObject({
onBlur: 'Please enter a different value',
})
})
it('should run async validation onBlur', async () => {
vi.useFakeTimers()
const form = new FormApi({
defaultValues: {
name: 'test',
},
onBlurAsync: async (value) => {
await sleep(1000)
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
expect(form.state.errors.length).toBe(0)
field.setValue('other', { touch: true })
field.validate('blur')
await vi.runAllTimersAsync()
expect(form.state.errors).toContain('Please enter a different value')
expect(form.state.errorMap).toMatchObject({
onBlur: 'Please enter a different value',
})
})
it('should run async validation onBlur with debounce', async () => {
vi.useFakeTimers()
const sleepMock = vi.fn().mockImplementation(sleep)
const form = new FormApi({
defaultValues: {
name: 'test',
},
onBlurAsyncDebounceMs: 1000,
onBlurAsync: async (value) => {
await sleepMock(10)
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
expect(form.state.errors.length).toBe(0)
field.setValue('other', { touch: true })
field.validate('blur')
field.validate('blur')
await vi.runAllTimersAsync()
// sleepMock will have been called 2 times without onBlurAsyncDebounceMs
expect(sleepMock).toHaveBeenCalledTimes(1)
expect(form.state.errors).toContain('Please enter a different value')
expect(form.state.errorMap).toMatchObject({
onBlur: 'Please enter a different value',
})
})
it('should run async validation onBlur with asyncDebounceMs', async () => {
vi.useFakeTimers()
const sleepMock = vi.fn().mockImplementation(sleep)
const form = new FormApi({
defaultValues: {
name: 'test',
},
asyncDebounceMs: 1000,
onBlurAsync: async (value) => {
await sleepMock(10)
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
expect(form.state.errors.length).toBe(0)
field.setValue('other', { touch: true })
field.validate('blur')
field.validate('blur')
await vi.runAllTimersAsync()
// sleepMock will have been called 2 times without asyncDebounceMs
expect(sleepMock).toHaveBeenCalledTimes(1)
expect(form.state.errors).toContain('Please enter a different value')
expect(form.state.errorMap).toMatchObject({
onBlur: 'Please enter a different value',
})
})
it('should contain multiple errors when running validation onBlur and onChange', () => {
const form = new FormApi({
defaultValues: {
name: 'other',
},
onBlur: (value) => {
if (value.name === 'other') return 'Please enter a different value'
return
},
onChange: (value) => {
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
field.setValue('other', { touch: true })
field.validate('blur')
expect(form.state.errors).toStrictEqual([
'Please enter a different value',
'Please enter a different value',
])
expect(form.state.errorMap).toEqual({
onBlur: 'Please enter a different value',
onChange: 'Please enter a different value',
})
})
it('should reset onChange errors when the issue is resolved', () => {
const form = new FormApi({
defaultValues: {
name: 'other',
},
onChange: (value) => {
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
field.setValue('other', { touch: true })
expect(form.state.errors).toStrictEqual(['Please enter a different value'])
expect(form.state.errorMap).toEqual({
onChange: 'Please enter a different value',
})
field.setValue('test', { touch: true })
expect(form.state.errors).toStrictEqual([])
expect(form.state.errorMap).toEqual({})
})
it('should return error onMount', () => {
const form = new FormApi({
defaultValues: {
name: 'other',
},
onMount: (value) => {
if (value.name === 'other') return 'Please enter a different value'
return
},
})
const field = new FieldApi({
form,
name: 'name',
})
form.mount()
field.mount()
expect(form.state.errors).toStrictEqual(['Please enter a different value'])
expect(form.state.errorMap).toEqual({
onMount: 'Please enter a different value',
})
})
})

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc