Skip to content

Commit e370413

Browse files
committed
fix: handle hoisted paths overriding one another
1 parent f688896 commit e370413

File tree

4 files changed

+42
-17
lines changed

4 files changed

+42
-17
lines changed

.changeset/spicy-rats-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'vee-validate': patch
3+
---
4+
5+
fix: handle hoisted paths overriding one another

packages/vee-validate/src/useField.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,9 @@ function _useField<TValue = unknown>(
175175

176176
async function validateCurrentValue(mode: SchemaValidationMode) {
177177
if (form?.validateSchema) {
178-
return (await form.validateSchema(mode)).results[toValue(name)] ?? { valid: true, errors: [] };
178+
const { results } = await form.validateSchema(mode);
179+
180+
return results[toValue(name)] ?? { valid: true, errors: [] };
179181
}
180182

181183
if (validator.value) {
@@ -199,7 +201,7 @@ function _useField<TValue = unknown>(
199201
},
200202
result => {
201203
if (flags.pendingUnmount[field.id]) {
202-
return;
204+
return result;
203205
}
204206

205207
setState({ errors: result.errors });

packages/vee-validate/src/useForm.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,9 @@ export function useForm<
351351

352352
const validateSchema = withLatest(
353353
async (mode: SchemaValidationMode) => {
354-
return (await mode) === 'silent' ? debouncedSilentValidation() : debouncedValidation();
354+
return (await (mode === 'silent'
355+
? debouncedSilentValidation()
356+
: debouncedValidation())) as FormValidationResult<TValues>;
355357
},
356358
(formResult, [mode]) => {
357359
// fields by id lookup
@@ -364,15 +366,19 @@ export function useForm<
364366
].sort() as string[];
365367

366368
// aggregates the paths into a single result object while applying the results on the fields
367-
return paths.reduce(
369+
const results = paths.reduce(
368370
(validation, _path) => {
369-
const path = _path as Path<TValues>;
370-
const pathState = findPathState(path) || findHoistedPath(path);
371-
const messages = (formResult.results[path] || { errors: [] as string[] }).errors;
372-
const fieldResult = {
373-
errors: messages,
374-
valid: !messages.length,
375-
};
371+
const expectedPath = _path as Path<TValues>;
372+
const pathState = findPathState(expectedPath) || findHoistedPath(expectedPath);
373+
const messages = formResult.results[expectedPath]?.errors || [];
374+
// This is the real path of the field, because it might've been a hoisted field
375+
const path = (toValue(pathState?.path) || expectedPath) as Path<TValues>;
376+
// It is possible that multiple paths are collected across loops
377+
// We want to merge them to avoid overriding any iteration's results
378+
const fieldResult = mergeValidationResults(
379+
{ errors: messages, valid: !messages.length },
380+
validation.results[path],
381+
);
376382
validation.results[path] = fieldResult;
377383
if (!fieldResult.valid) {
378384
validation.errors[path] = fieldResult.errors[0];
@@ -406,6 +412,8 @@ export function useForm<
406412
},
407413
{ valid: formResult.valid, results: {}, errors: {} } as FormValidationResult<TValues>,
408414
);
415+
416+
return results;
409417
},
410418
);
411419

@@ -1183,3 +1191,14 @@ function useFormInitialValues<TValues extends GenericObject>(
11831191
setInitialValues,
11841192
};
11851193
}
1194+
1195+
function mergeValidationResults(a: ValidationResult, b?: ValidationResult): ValidationResult {
1196+
if (!b) {
1197+
return a;
1198+
}
1199+
1200+
return {
1201+
valid: a.valid && b.valid,
1202+
errors: [...a.errors, ...b.errors],
1203+
};
1204+
}

packages/vee-validate/src/utils/common.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,10 @@ export function applyModelModifiers<TValue = unknown>(value: TValue, modifiers:
246246
return value;
247247
}
248248

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

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

263263
latestRun = undefined;
264-
onDone(result, args);
265264

266-
return result;
265+
return onDone(result, args);
267266
};
268267
}
269268

0 commit comments

Comments
 (0)