-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
What happened?
Sorry for not having more time to write a detailed bug report.
I am using fieldArray with object-like fields, represented by this zod schema:
const notificationChannelsSchema = z.array(z.object({
id: nonEmptyString('Notification channel ID is required.', 'Notification channel ID is invalid.'),
name: nonEmptyString('Notification channel name is required.', 'Notification channel name is invalid.'),
}))I have a button which dynamically adds notification channel input elements with the following names:
notificationChannels[$idx].idnotificationChannels[$idx].name
I use validate on mount, but when the field gets mounted, errors object from useFormContext contains only the error message for the name field, and not for the id field, even though both of them are initially empty and required.
I asked Opus 4.5 to add debug logs to vee-validate.mjs and it discovered a discrepancy beteween dot notation and bracked notation in error paths (id field gets dot notation, name field gets bracket notation). It has something to do with debouncing and withLatest function and onDone callback. I don't have time to dig deeper into that.
However, it suggested the fix which works great:
Working fix recommended by Opus 4.5
diff --git a/dist/vee-validate.mjs b/dist/vee-validate.mjs
index 1234567..abcdefg 100644
--- a/dist/vee-validate.mjs
+++ b/dist/vee-validate.mjs
@@ -1003,7 +1003,9 @@ async function validateStandardSchema(schema, values) {
const combinedIssues = combineStandardIssues(validationResult.issues || []);
for (const error of combinedIssues) {
const messages = error.messages;
- const path = error.path;
+ // FIX: Normalize path from dot notation (notificationChannels.0.id) to bracket notation (notificationChannels[0].id)
+ // This ensures consistency with how fields are registered via useField
+ const path = normalizeFormPath(error.path);
results[path] = { valid: !messages.length, errors: messages };
if (messages.length) {
errors[path] = messages[0];Short explanation from AI
The Problem with withLatest
withLatest is designed to only run onDone once for the last caller:
function withLatest(fn, onDone) {
let latestRun; // Single shared variable
return async function runLatest(...args) {
const pending = fn(...args); // Creates a NEW promise
latestRun = pending; // Overwrites previous
const result = await pending;
if (pending !== latestRun) {
return result; // ❌ Earlier caller - NO transformation
}
return onDone(result, args); // ✅ Latest caller - WITH transformation
};
}
Why both don't get bracket notation:
- .id calls validateSchema() → creates promise_A, sets latestRun = promise_A
- .name calls validateSchema() → creates promise_B, sets latestRun = promise_B (overwrites!)
- Both promises resolve with the same raw data (dot notation from schema)
- .id's promise checks: promise_A !== latestRun (it's promise_B now) → returns RAW (dot)
- .name's promise checks: promise_B === latestRun → runs onDone → returns transformed (bracket)
The design flaw:
withLatest assumes onDone has side effects that should only happen once (like setting errors on all fields). But it also transforms the return value, which means earlier callers get untransformed data.
This is why my fix normalizes paths inside validateStandardSchema (before withLatest sees it), so the raw data already has bracket notation - then it doesn't matter if onDone runs or not.
TL;DR: withLatest intentionally only transforms the last caller's result. Earlier callers get raw data. That's the bug when the transformation includes path normalization.
Reproduction steps
...
Version
Vue.js 3.x and vee-validate 4.x
What browsers are you seeing the problem on?
- Firefox
- Chrome
- Safari
- Microsoft Edge
Relevant log output
Demo link
.
Code of Conduct
- I agree to follow this project's Code of Conduct