Skip to content

Re-Add Form-level Validation Logic #466

Closed

Description

@tannerlinsley

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

Activity

  1. hiddentao commented on Sep 24, 2023

    @hiddentao

    Yes please. Just missing this now.

  2. crutchcorn commented on Oct 9, 2023

    @crutchcorn
    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)
    }

  3. removed their assignment
    on Oct 31, 2023
  4. crutchcorn commented on Oct 31, 2023

    @crutchcorn
    Member

    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.

  5. self-assigned this
    on Oct 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions