Skip to content

fix: radio buttons with same name handle errors correctly (#5001)#5147

Open
logaretm wants to merge 1 commit intomainfrom
fix/5001-radio-same-name
Open

fix: radio buttons with same name handle errors correctly (#5001)#5147
logaretm wants to merge 1 commit intomainfrom
fix/5001-radio-same-name

Conversation

@logaretm
Copy link
Owner

@logaretm logaretm commented Mar 4, 2026

Summary

  • Fixes Radiobutton same name errors problem #5001: When multiple <Field> components share the same name (e.g. radio buttons), only the last one would receive validation errors
  • The root cause was that createPathState only reused existing path states for fields explicitly typed as checkbox or radio. When type was not specified on the <Field> component (common when wrapping radio inputs in a custom component), separate path states were created, and only the last one in the lookup would receive errors from setFieldError
  • The fix makes createPathState reuse existing path states for ALL fields sharing the same path, not just checkbox/radio types. It also updates removePathState and unmount logic to properly handle shared non-multiple fields

Changes

  • packages/vee-validate/src/useForm.ts: Broadened the createPathState reuse condition to match any field with the same path (with a normalized path identity check to avoid stale field array paths). Updated removePathState to decrement fieldsCount for all shared fields and only remove the path state when fieldsCount <= 0
  • packages/vee-validate/src/useField.ts: Updated the unmount ID matching logic to handle array IDs regardless of multiple flag. Only unsets path values when it is the last field or a radio/checkbox group
  • packages/vee-validate/tests/Form.spec.ts: Added two regression tests for Radiobutton same name errors problem #5001 and updated the Fields don't clean up their rules properly #4643 test to reflect the new shared pathState behavior

Test plan

  • New test: radio buttons without type="radio" on Field should all show errors when they share the same name
  • New test: radio buttons with type="radio" and same name should all show errors via v-slot
  • Updated existing test for Fields don't clean up their rules properly #4643 to match new shared pathState structure
  • All 357 existing tests pass (3 pre-existing failures in unrelated suites: i18n, toTypedSchema, validate.spec)

🤖 Generated with Claude Code

When multiple Field components share the same name (e.g. radio buttons),
only the last one would receive validation errors. This happened because
createPathState only reused existing path states for fields explicitly
typed as checkbox/radio, causing separate path states to be created when
type was not specified on the Field component.

The fix makes createPathState reuse existing path states for ALL fields
sharing the same path, not just checkbox/radio types. This ensures:

- All fields with the same name share a single PathState and its errors
- removePathState properly decrements fieldsCount for all shared fields
- Unmount logic handles array IDs for non-multiple shared fields
- Path values are only unset when the last field unmounts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 4, 2026 07:08
@changeset-bot
Copy link

changeset-bot bot commented Mar 4, 2026

🦋 Changeset detected

Latest commit: ab611b8

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

@netlify
Copy link

netlify bot commented Mar 4, 2026

Deploy Preview for vee-validate-v5 ready!

Name Link
🔨 Latest commit ab611b8
🔍 Latest deploy log https://app.netlify.com/projects/vee-validate-v5/deploys/69a7da68fbf3ef00078f7514
😎 Deploy Preview https://deploy-preview-5147--vee-validate-v5.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Mar 4, 2026

Deploy Preview for vee-validate-docs canceled.

Name Link
🔨 Latest commit ab611b8
🔍 Latest deploy log https://app.netlify.com/projects/vee-validate-docs/deploys/69a7da6862d5bb00084f4d88

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a regression where multiple <Field> components sharing the same name (notably radio button groups) would not all receive/reflect validation errors, by making useForm reuse a single shared PathState per normalized path and adjusting unmount/removal behavior accordingly.

Changes:

  • Broaden createPathState reuse to any field sharing the same normalized path; adjust removePathState to decrement fieldsCount for all shared states and remove only when fieldsCount <= 0.
  • Update useField unmount cleanup to match array ids regardless of multiple, and to avoid unsetting shared path values except when appropriate.
  • Add regression tests for #5001 and update the #4643 test expectations to reflect the new shared PathState structure; add a changeset entry.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/vee-validate/src/useForm.ts Reuses PathState for all fields sharing a normalized path; updates shared-state teardown semantics.
packages/vee-validate/src/useField.ts Adjusts unmount id matching for shared states and changes when path values are unset during cleanup.
packages/vee-validate/tests/Form.spec.ts Updates #4643 test to shared state shape; adds two new regression tests for shared radio errors (#5001).
.changeset/fix-5001-radio-same-name.md Declares a patch release note for the #5001 fix.
Comments suppressed due to low confidence (1)

packages/vee-validate/src/useForm.ts:283

  • createPathState now reuses an existing PathState for any field that shares the same path, but the reuse branch doesn't reconcile per-field config like validate/bails/type/label. Because useForm.validate() and form submissions rely on pathState.validate, this can cause the form to validate using the first mounted field's validator even when a later field with the same name has different rules (e.g. the #4643 scenario: useField('foo') registers an always-valid validator, then a <Field name="foo" rules="required" /> reuses the state but doesn't update the validator, so submit/validate() can incorrectly pass). Consider tracking validators per field id and selecting/combining the active one, or otherwise updating pathState.validate on reuse and restoring it appropriately on unmount. Adding a regression test that submits/validates in the #4643 scenario would help prevent this.
    const pathValue = toValue(path);
    const pathStateExists = pathStateLookup.value[pathValue];
    const isCheckboxOrRadio = config?.type === 'checkbox' || config?.type === 'radio';
    if (pathStateExists && normalizeFormPath(toValue(pathStateExists.path)) === normalizeFormPath(pathValue)) {
      if (isCheckboxOrRadio) {
        pathStateExists.multiple = true;
      }

      const id = FIELD_ID_COUNTER++;
      if (Array.isArray(pathStateExists.id)) {
        pathStateExists.id.push(id);
      } else {
        pathStateExists.id = [pathStateExists.id, id];
      }

      pathStateExists.fieldsCount++;
      pathStateExists.__flags.pendingUnmount[id] = false;

      return pathStateExists as PathState<TValues[TPath], TOutput[TPath]>;

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +405 to 406
} else if (pathState?.multiple || pathState?.fieldsCount <= 1) {
form.unsetPathValue(toValue(name));
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unmount cleanup unsets the path value whenever pathState.multiple is true, even when unmounting a radio option that isn't currently selected. For radio groups multiple is set to true but pathState.value is a scalar, so removing any option will clear the whole model value (and may unintentionally uncheck the remaining radios). Consider only unsetting for radios when the current value matches the unmounted option’s checkedValue (or when it’s the last remaining field), rather than unconditionally for any multiple pathState.

Suggested change
} else if (pathState?.multiple || pathState?.fieldsCount <= 1) {
form.unsetPathValue(toValue(name));
} else if (pathState) {
const isMultiple = !!pathState.multiple;
const hasFieldsCount = typeof pathState.fieldsCount === 'number';
const isLastField = hasFieldsCount && pathState.fieldsCount <= 1;
const isRadioLike = isMultiple && !Array.isArray(pathState.value);
const shouldUnsetForRadio =
isRadioLike && (isLastField || isEqual(pathState.value, toValue(field.checkedValue)));
const shouldUnsetForSingle = !isMultiple && isLastField;
if (shouldUnsetForRadio || shouldUnsetForSingle) {
form.unsetPathValue(path);
}

Copilot uses AI. Check for mistakes.
Comment on lines +3245 to +3251
const REQUIRED_MSG = 'This field is required';
defineRule('required', (value: unknown) => {
if (!value) {
return REQUIRED_MSG;
}
return true;
});
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file already defines the required rule and REQUIRED_MESSAGE at the top of the suite. Re-defining required inside these new tests is redundant and mutates global rule state, which can make future tests added after these ones order-dependent. Prefer reusing the existing REQUIRED_MESSAGE and rule registration instead of calling defineRule('required', ...) again here.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Radiobutton same name errors problem

2 participants