Skip to content

Commit

Permalink
fix: handle hoisted paths overriding one another
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Nov 25, 2023
1 parent f688896 commit e370413
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 17 deletions.
5 changes: 5 additions & 0 deletions .changeset/spicy-rats-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vee-validate': patch
---

fix: handle hoisted paths overriding one another
6 changes: 4 additions & 2 deletions packages/vee-validate/src/useField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,9 @@ function _useField<TValue = unknown>(

async function validateCurrentValue(mode: SchemaValidationMode) {
if (form?.validateSchema) {
return (await form.validateSchema(mode)).results[toValue(name)] ?? { valid: true, errors: [] };
const { results } = await form.validateSchema(mode);

return results[toValue(name)] ?? { valid: true, errors: [] };
}

if (validator.value) {
Expand All @@ -199,7 +201,7 @@ function _useField<TValue = unknown>(
},
result => {
if (flags.pendingUnmount[field.id]) {
return;
return result;
}

setState({ errors: result.errors });
Expand Down
37 changes: 28 additions & 9 deletions packages/vee-validate/src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,9 @@ export function useForm<

const validateSchema = withLatest(
async (mode: SchemaValidationMode) => {
return (await mode) === 'silent' ? debouncedSilentValidation() : debouncedValidation();
return (await (mode === 'silent'
? debouncedSilentValidation()
: debouncedValidation())) as FormValidationResult<TValues>;
},
(formResult, [mode]) => {
// fields by id lookup
Expand All @@ -364,15 +366,19 @@ export function useForm<
].sort() as string[];

// aggregates the paths into a single result object while applying the results on the fields
return paths.reduce(
const results = paths.reduce(
(validation, _path) => {
const path = _path as Path<TValues>;
const pathState = findPathState(path) || findHoistedPath(path);
const messages = (formResult.results[path] || { errors: [] as string[] }).errors;
const fieldResult = {
errors: messages,
valid: !messages.length,
};
const expectedPath = _path as Path<TValues>;
const pathState = findPathState(expectedPath) || findHoistedPath(expectedPath);
const messages = formResult.results[expectedPath]?.errors || [];
// This is the real path of the field, because it might've been a hoisted field
const path = (toValue(pathState?.path) || expectedPath) as Path<TValues>;
// It is possible that multiple paths are collected across loops
// We want to merge them to avoid overriding any iteration's results
const fieldResult = mergeValidationResults(
{ errors: messages, valid: !messages.length },
validation.results[path],
);
validation.results[path] = fieldResult;
if (!fieldResult.valid) {
validation.errors[path] = fieldResult.errors[0];
Expand Down Expand Up @@ -406,6 +412,8 @@ export function useForm<
},
{ valid: formResult.valid, results: {}, errors: {} } as FormValidationResult<TValues>,
);

return results;
},
);

Expand Down Expand Up @@ -1183,3 +1191,14 @@ function useFormInitialValues<TValues extends GenericObject>(
setInitialValues,
};
}

function mergeValidationResults(a: ValidationResult, b?: ValidationResult): ValidationResult {
if (!b) {
return a;
}

return {
valid: a.valid && b.valid,
errors: [...a.errors, ...b.errors],
};
}
11 changes: 5 additions & 6 deletions packages/vee-validate/src/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,10 @@ export function applyModelModifiers<TValue = unknown>(value: TValue, modifiers:
return value;
}

export function withLatest<TFunction extends (...args: any[]) => Promise<any>, TResult = ReturnType<TFunction>>(
fn: TFunction,
onDone: (result: Awaited<TResult>, args: Parameters<TFunction>) => void,
) {
export function withLatest<
TFunction extends (...args: any[]) => Promise<any>,
TResult = Awaited<ReturnType<TFunction>>,
>(fn: TFunction, onDone: (result: TResult, args: Parameters<TFunction>) => TResult) {
let latestRun: Promise<TResult> | undefined;

return async function runLatest(...args: Parameters<TFunction>) {
Expand All @@ -261,9 +261,8 @@ export function withLatest<TFunction extends (...args: any[]) => Promise<any>, T
}

latestRun = undefined;
onDone(result, args);

return result;
return onDone(result, args);
};
}

Expand Down

0 comments on commit e370413

Please sign in to comment.