From d68d455007ae7bdbd266aaa45cac41c5efcdc048 Mon Sep 17 00:00:00 2001 From: Christian Luis Date: Mon, 31 Jul 2023 21:14:18 -0300 Subject: [PATCH 1/2] feat(useForm): Set errors only for touched fields on 'blur' validation mode --- packages/core/src/composables/useForm.ts | 40 ++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/packages/core/src/composables/useForm.ts b/packages/core/src/composables/useForm.ts index 529b746..d24dcbd 100644 --- a/packages/core/src/composables/useForm.ts +++ b/packages/core/src/composables/useForm.ts @@ -49,6 +49,10 @@ interface FieldArrayRegistry { }; } +interface ValidateHandlerOptions { + onlyBlurred?: boolean; +} + export interface FormSubmitHelper { setSubmitting: (isSubmitting: boolean) => void; readonly initialValues: Values; @@ -262,7 +266,7 @@ export function useForm< }); return validateTiming.value === 'blur' - ? runAllValidateHandler(state.values) + ? runAllValidateHandler(state.values, { onlyBlurred: true }) : Promise.resolve(); }; @@ -503,20 +507,50 @@ export function useForm< }); }; - const runAllValidateHandler = (values: Values = state.values) => { + /** + * Creates a new object of errors, but only with the errors of touched fields. + * + * @param errors The union of field and form errors + * @returns The field errors that have been touched + */ + const getTouchedErrors = (errors: FormErrors) => { + const touchedFieldsKeys: any = []; + + Object.entries(state.touched.value).forEach(([key, isTouched]) => { + if (isTouched) { + touchedFieldsKeys.push(key); + } + }); + + const touchedErrorsEntries: Array<[keyof FormErrors, string]> = + Object.entries(errors).filter(([key]) => { + return touchedFieldsKeys.includes(key); + }); + + return Object.fromEntries(touchedErrorsEntries) as FormErrors; + }; + + const runAllValidateHandler = ( + values: Values = state.values, + validateOptions?: ValidateHandlerOptions, + ) => { dispatch({ type: ACTION_TYPE.SET_ISVALIDATING, payload: true }); return Promise.all([ runFieldValidateHandler(values), options.validate ? runValidateHandler(values) : {}, ]) .then(([fieldErrors, validateErrors]) => { - const errors = deepmerge.all>( + const baseErrors = deepmerge.all>( [fieldErrors, validateErrors], { arrayMerge, }, ); + const errors = validateOptions?.onlyBlurred + ? getTouchedErrors(baseErrors) + : baseErrors; + setErrors(errors); return errors; From 6e217dc19d7f8b4e6a441405f5172f71f2b364f5 Mon Sep 17 00:00:00 2001 From: Christian Luis Date: Mon, 7 Aug 2023 21:42:59 -0300 Subject: [PATCH 2/2] feat(useForm): Update getTouchedErros to allow deep nested errors/touched objects --- packages/core/src/composables/useForm.ts | 36 ++++++++++++++---------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/core/src/composables/useForm.ts b/packages/core/src/composables/useForm.ts index d24dcbd..d59065f 100644 --- a/packages/core/src/composables/useForm.ts +++ b/packages/core/src/composables/useForm.ts @@ -1,7 +1,7 @@ import deepmerge from 'deepmerge'; import isEqual from 'fast-deep-equal/es6'; import { klona as deepClone } from 'klona/full'; -import { computed, onMounted, provide, reactive, ref } from 'vue'; +import { computed, onMounted, provide, reactive, ref, unref } from 'vue'; import toValue from './toValue'; import { FormContextKey } from './useFormContext'; @@ -513,21 +513,27 @@ export function useForm< * @param errors The union of field and form errors * @returns The field errors that have been touched */ - const getTouchedErrors = (errors: FormErrors) => { - const touchedFieldsKeys: any = []; - - Object.entries(state.touched.value).forEach(([key, isTouched]) => { - if (isTouched) { - touchedFieldsKeys.push(key); + const getTouchedErrors = < + T extends FormErrors, + F extends FormTouched, + >( + errors: T, + touched: F, + ): Partial => { + const result: any = {}; + + for (const key in errors) { + if (typeof touched[key] === 'object' && typeof errors[key] === 'object') { + result[key] = getTouchedErrors( + errors[key] as FormErrors, + touched[key] as FormTouched, + ); + } else if (touched[key] === true && errors[key]) { + result[key] = errors[key]; } - }); - - const touchedErrorsEntries: Array<[keyof FormErrors, string]> = - Object.entries(errors).filter(([key]) => { - return touchedFieldsKeys.includes(key); - }); + } - return Object.fromEntries(touchedErrorsEntries) as FormErrors; + return result; }; const runAllValidateHandler = ( @@ -548,7 +554,7 @@ export function useForm< ); const errors = validateOptions?.onlyBlurred - ? getTouchedErrors(baseErrors) + ? getTouchedErrors(baseErrors, unref(state.touched)) : baseErrors; setErrors(errors);