fix: show validation error when field array is empty (#4981)#5143
fix: show validation error when field array is empty (#4981)#5143
Conversation
When all items are removed from a field array, validation errors for the array itself (e.g., min length) were being incorrectly hoisted to a parent path by findHoistedPath. This fix skips hoisting when the error path matches a known field array path, ensuring the error is placed at the correct path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 5657037 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for vee-validate-docs canceled.
|
✅ Deploy Preview for vee-validate-v5 ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Fixes vee-validate schema error placement for field arrays becoming empty (e.g. z.array().min(1)), ensuring array-level errors are not incorrectly hoisted to a parent path state.
Changes:
- Adjust
validateSchemapath-state resolution to skipfindHoistedPathwhen the schema error path matches a registered field array path. - Add regression tests for top-level and nested object field arrays becoming empty.
- Add a changeset entry for a patch release.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| packages/vee-validate/src/useForm.ts | Skips hoisting schema errors for known field-array paths so errors land on the array path. |
| packages/vee-validate/tests/useFieldArray.spec.ts | Adds regression tests asserting array-level errors appear at the correct path when all items are removed. |
| .changeset/fix-4981-empty-array-error.md | Declares a patch changeset for the fix. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const isFieldArrayPath = fieldArrays.some(a => toValue(a.path) === expectedPath); | ||
| const pathState = | ||
| findPathState(expectedPath) || (isFieldArrayPath ? undefined : findHoistedPath(expectedPath)); |
There was a problem hiding this comment.
isFieldArrayPath compares toValue(a.path) and expectedPath as raw strings. Standard-schema issues are converted using getDotPath, which yields dot-index paths (e.g. users.0.friends), while vee-validate commonly normalizes array indices to bracket syntax (e.g. users[0].friends). If a field array path includes numeric indices, this equality check will fail and hoisting will still occur, leaving the original bug unfixed for those cases. Consider normalizing both sides (and ideally precomputing a normalized Set of field array paths once per validation run) before doing the comparison.
| mountWithHoc({ | ||
| setup() { | ||
| form = useForm<any>({ | ||
| initialValues: { | ||
| users: ['one'], | ||
| }, | ||
| validationSchema: z.object({ | ||
| users: z.array(z.string()).min(1, 'At least one item is required'), | ||
| }), | ||
| }); | ||
|
|
||
| arr = useFieldArray('users'); | ||
| }, | ||
| template: ` | ||
| <div></div> | ||
| `, | ||
| }); |
There was a problem hiding this comment.
These new tests mount an empty template and only call useForm + useFieldArray, which (per useFieldArray.ts) does not register any PathStates. Without a parent PathState candidate, findHoistedPath() would return undefined even on the pre-fix code path, so the tests may pass without actually covering the hoisting bug described in #4981. Consider rendering/registering a parent field (e.g. a <Field name="settings" /> / useField('settings'), or reproducing the setFieldValue('settings', ...) pattern from the issue) so the tests fail on the buggy behavior and validate the fix.
Summary
z.array().min(1)) were not being shown at the correct error path.findHoistedPathinvalidateSchemaincorrectly hoisting array-level errors to a parent path state (e.g., error atmySettings.myArraywas hoisted tomySettings).extraErrorsBag.Test plan
🤖 Generated with Claude Code