Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-Add Form-level Validation Logic #466

Closed
tannerlinsley opened this issue Sep 14, 2023 · 3 comments · Fixed by #505
Closed

Re-Add Form-level Validation Logic #466

tannerlinsley opened this issue Sep 14, 2023 · 3 comments · Fixed by #505
Assignees

Comments

@tannerlinsley
Copy link
Collaborator

tannerlinsley commented Sep 14, 2023

Somewhere in our refactoring, the form-level validation logic was removed. We need to:

  • Add it back
  • Determine how it fits in with schema-based validation @crutchcorn is working on
  • Potentially abstract shared logic to utils
@hiddentao
Copy link

Yes please. Just missing this now.

@crutchcorn
Copy link
Member

Adding a bit of context, maybe @aadito123 can tackle while I'm working on schema based validation:

This code was removed here: 884235f

And needs to be added back akin to this bit of code:

validateAsync = async (value = this.state.value, cause: ValidationCause) => {
const {
onChangeAsync,
onBlurAsync,
onSubmitAsync,
asyncDebounceMs,
onBlurAsyncDebounceMs,
onChangeAsyncDebounceMs,
} = this.options
const validate =
cause === 'change'
? onChangeAsync
: cause === 'submit'
? onSubmitAsync
: onBlurAsync
if (!validate) return []
const debounceMs =
cause === 'submit'
? 0
: (cause === 'change'
? onChangeAsyncDebounceMs
: onBlurAsyncDebounceMs) ??
asyncDebounceMs ??
0
if (this.state.meta.isValidating !== true)
this.setMeta((prev) => ({ ...prev, isValidating: true }))
// Use the validationCount for all field instances to
// track freshness of the validation
const validationAsyncCount = this.#leaseValidateAsync()
const checkLatest = () =>
validationAsyncCount === this.getInfo().validationAsyncCount
if (!this.getInfo().validationPromise) {
this.getInfo().validationPromise = new Promise((resolve, reject) => {
this.getInfo().validationResolve = resolve
this.getInfo().validationReject = reject
})
}
if (debounceMs > 0) {
await new Promise((r) => setTimeout(r, debounceMs))
}
// Only kick off validation if this validation is the latest attempt
if (checkLatest()) {
const prevErrors = this.getMeta().errors
try {
const rawError = await validate(value as never, this as never)
if (checkLatest()) {
const error = normalizeError(rawError)
this.setMeta((prev) => ({
...prev,
isValidating: false,
errorMap: {
...prev.errorMap,
[getErrorMapKey(cause)]: error,
},
}))
this.getInfo().validationResolve?.([...prevErrors, error])
}
} catch (error) {
if (checkLatest()) {
this.getInfo().validationReject?.([...prevErrors, error])
throw error
}
} finally {
if (checkLatest()) {
this.setMeta((prev) => ({ ...prev, isValidating: false }))
delete this.getInfo().validationPromise
}
}
}
// Always return the latest validation promise to the caller
return this.getInfo().validationPromise ?? []
}
validate = (
cause: ValidationCause,
value?: TData,
): ValidationError[] | Promise<ValidationError[]> => {
// If the field is pristine and validatePristine is false, do not validate
if (!this.state.meta.isTouched) return []
// Attempt to sync validate first
this.validateSync(value, cause)
const errorMapKey = getErrorMapKey(cause)
// If there is an error mapped to the errorMapKey (eg. onChange, onBlur, onSubmit), return the errors array, do not attempt async validation
if (this.getMeta().errorMap[errorMapKey]) {
if (!this.options.asyncAlways) {
return this.state.meta.errors
}
}
// No error? Attempt async validation
return this.validateAsync(value, cause)
}

@crutchcorn
Copy link
Member

crutchcorn commented Oct 31, 2023

After talking with @aadito123, who's working on this issue (reassigning the GH issue, evidently can't), I think we need to change our formError to:

export type FormState<TData> = {
  formErrors?: ValidationError[]
  // Computed, just like `FieldAPI`
  formErrorMap?: ValidationErrorMap
  
  // Not in this PR, but in some follow-up
  fieldErrors?: Record<DeepKeys<TData>, ValidationError[]>
  fieldErrorMap?: Record<DeepKeys<TData>, ValidationErrorMap>

  // ...
}

So that our users can both:

  • See the form errors on a per-validator method
  • Match FieldAPI's API
  • See all fields' errors to render them somewhere outside of Field if required

Update

@aadito123 correctly pointed out that we don't need fieldErrors, as we already have fieldMeta.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants