Skip to content

v5: validateStandardSchema - inconsistent dot notation VS bracket notation in error.path #5108

@edosrecki

Description

@edosrecki

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].id
  • notificationChannels[$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:

  1. .id calls validateSchema() → creates promise_A, sets latestRun = promise_A
  2. .name calls validateSchema() → creates promise_B, sets latestRun = promise_B (overwrites!)
  3. Both promises resolve with the same raw data (dot notation from schema)
  4. .id's promise checks: promise_A !== latestRun (it's promise_B now) → returns RAW (dot)
  5. .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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions