Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ export interface FieldListeners<
onMount?: FieldListenerFn<TParentData, TName, TData>
onUnmount?: FieldListenerFn<TParentData, TName, TData>
onSubmit?: FieldListenerFn<TParentData, TName, TData>
onGroupSubmit?: FieldListenerFn<TParentData, TName, TData>
}

/**
Expand Down
75 changes: 53 additions & 22 deletions packages/form-core/src/FormApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,13 @@ export type AnyFormApi = FormApi<
any
>

interface ValidateOpts<TFormData> {
// Useful in FormGroup where validation doesn't update form error map
dontUpdateFormErrorMap?: boolean
// Filter which field names to validate, useful for FormGroup validation to filter out fields that don't start with the FormGroup name
filterFieldNames?: (fieldName: DeepKeys<TFormData>) => boolean
}

/**
* We cannot use methods and must use arrow functions. Otherwise, our React adapters
* will break due to loss of the method when using spread.
Expand Down Expand Up @@ -1654,6 +1661,7 @@ export class FormApi<
*/
validateSync = (
cause: ValidationCause,
validateOpts?: ValidateOpts<TFormData>,
): {
hasErrored: boolean
fieldsErrorMap: FormErrorMapFromValidator<
Expand Down Expand Up @@ -1709,11 +1717,17 @@ export class FormApi<

const errorMapKey = getErrorMapKey(validateObj.cause)

const allFieldsToProcess = new Set([
let allFieldsToProcess = new Set([
...Object.keys(this.state.fieldMeta),
...Object.keys(fieldErrors || {}),
] as DeepKeys<TFormData>[])

if (validateOpts?.filterFieldNames) {
allFieldsToProcess = new Set(
[...allFieldsToProcess].filter(validateOpts.filterFieldNames),
)
}

for (const field of allFieldsToProcess) {
if (
this.baseStore.state.fieldMetaBase[field] === undefined &&
Expand Down Expand Up @@ -1765,22 +1779,28 @@ export class FormApi<
}
}

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (this.state.errorMap?.[errorMapKey] !== formError) {
this.baseStore.setState((prev) => ({
...prev,
errorMap: {
...prev.errorMap,
[errorMapKey]: formError,
},
}))
if (!validateOpts?.dontUpdateFormErrorMap) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (this.state.errorMap?.[errorMapKey] !== formError) {
this.baseStore.setState((prev) => ({
...prev,
errorMap: {
...prev.errorMap,
[errorMapKey]: formError,
},
}))
}
}

if (formError || fieldErrors) {
hasErrored = true
}
}

if (validateOpts?.dontUpdateFormErrorMap) {
return
}

/**
* when we have an error for onSubmit in the state, we want
* to clear the error as soon as the user enters a valid value in the field
Expand Down Expand Up @@ -1830,6 +1850,7 @@ export class FormApi<
*/
validateAsync = async (
cause: ValidationCause,
validateOpts?: ValidateOpts<TFormData>,
): Promise<
FormErrorMapFromValidator<
TFormData,
Expand Down Expand Up @@ -1920,9 +1941,13 @@ export class FormApi<
}
const errorMapKey = getErrorMapKey(validateObj.cause)

for (const field of Object.keys(
this.state.fieldMeta,
) as DeepKeys<TFormData>[]) {
let fields: DeepKeys<TFormData>[] = Object.keys(this.state.fieldMeta)

if (validateOpts?.filterFieldNames) {
fields = fields.filter(validateOpts.filterFieldNames)
}

for (const field of fields) {
if (this.baseStore.state.fieldMetaBase[field] === undefined) {
continue
}
Expand Down Expand Up @@ -1965,13 +1990,15 @@ export class FormApi<
}
}

this.baseStore.setState((prev) => ({
...prev,
errorMap: {
...prev.errorMap,
[errorMapKey]: formError,
},
}))
if (!validateOpts?.dontUpdateFormErrorMap) {
this.baseStore.setState((prev) => ({
...prev,
errorMap: {
...prev.errorMap,
[errorMapKey]: formError,
},
}))
}

resolve(
fieldErrorsFromFormValidators
Expand Down Expand Up @@ -2030,6 +2057,7 @@ export class FormApi<
*/
validate = (
cause: ValidationCause,
validateOpts?: ValidateOpts<TFormData>,
):
| FormErrorMapFromValidator<
TFormData,
Expand Down Expand Up @@ -2058,14 +2086,17 @@ export class FormApi<
>
> => {
// Attempt to sync validate first
const { hasErrored, fieldsErrorMap } = this.validateSync(cause)
const { hasErrored, fieldsErrorMap } = this.validateSync(
cause,
validateOpts,
)

if (hasErrored && !this.options.asyncAlways) {
return fieldsErrorMap
}

// No error? Attempt async validation
return this.validateAsync(cause)
return this.validateAsync(cause, validateOpts)
}

// Needs to edgecase in the React adapter specifically to avoid type errors
Expand Down
Loading
Loading