Skip to content

fix: Bubble Select change events to forms#10325

Open
wanxiankai wants to merge 4 commits into
adobe:mainfrom
wanxiankai:fix/select-change-event
Open

fix: Bubble Select change events to forms#10325
wanxiankai wants to merge 4 commits into
adobe:mainfrom
wanxiankai:fix/select-change-event

Conversation

@wanxiankai

@wanxiankai wanxiankai commented Jul 14, 2026

Copy link
Copy Markdown

Closes #6217

Summary

React Aria Select updates its hidden native select when an option is chosen, but React rendering alone does not emit a bubbling native change event. As a result, patterns such as <form onChange={handler}> do not observe Select changes.

This change:

  • adds a Select state subscription that only emits for user-driven ListBox selection changes;
  • synchronizes the hidden native select before dispatching a bubbling change event;
  • avoids feeding the generated event back into Select state;
  • preserves native/autofill events without duplicating them;
  • supports controlled and multiple-selection Selects without emitting on form reset;
  • preserves form, validation, and change event semantics for collections over 300 items without rendering every option into the native select.

This is a focused follow-up to the reset, controlled-value, and large-collection concerns discussed in #6320. The state-subscription direction also follows the Select prototype in #8426, scoped to the current Select implementation and its multiple-selection API.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added unit tests; no Storybook update is needed because this does not add a visual state.
  • Filled out test instructions.
  • Documentation updates are not required because this restores native form event behavior without changing the component props.
  • Looked at the Accessibility Practices for this feature - Aria Practices.

📝 Test Instructions:

Run:

yarn jest packages/react-aria-components/test/Select.test.js packages/react-aria/test/select/HiddenSelect.test.tsx --runInBand
yarn lint

The regression tests cover uncontrolled, controlled, native/autofill, form reset, multiple-selection, and over-300-item collection behavior. To verify manually, wrap a Select in a form with an onChange handler, choose an option from the ListBox, and confirm the handler receives one event whose target is the hidden native select with the selected value.

🧢 Your Project:

Independent contribution.

@wanxiankai
wanxiankai marked this pull request as ready for review July 14, 2026 03:15

@snowystinger snowystinger left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR. Did you review earlier attempts at solving this?
#6320
#8426

What are the differences or similarities in the approach you've taken?

Comment thread packages/react-aria-components/test/Select.test.js Outdated
Comment thread packages/react-aria-components/test/Select.test.js
@wanxiankai

Copy link
Copy Markdown
Author

Thanks for pointing me to the previous attempts. I reviewed both in detail.

#6320 dispatched change from an effect that watched selectedKey. That meant form resets could also emit a change, controlled Pickers were still unresolved, and the author called out the over-300-items hidden-input path as uncovered.

#8426 introduced a reusable state-event/input-event mechanism across several form components. This PR follows the same state-subscription direction, but keeps the scope to Select and its current single/multiple-selection API. The subscription is emitted only from the ListBox user-selection path, so form reset and native/autofill changes do not generate duplicate events. Before dispatch, the hidden select is synchronized so controlled and multiple-selection handlers observe the proposed value synchronously.

I also addressed the large-collection gap in b4282f7. For collections over 300 items, HiddenSelect now keeps a native select containing only the current values rather than rendering every option. Missing values are added temporarily during synchronous dispatch, so form change handlers receive the complete selectedOptions while FormData and required validation continue to work without a 300+ option DOM cost.

The two review tests were clarified as well: the native/autofill test is now explicitly distinguished from the ListBox-selection test, and the controlled test now uses a stateful parent that accepts the update.

Verification:

  • yarn jest packages/react-aria-components/test/Select.test.js packages/react-aria/test/select/HiddenSelect.test.tsx --runInBand (43 tests passed)
  • yarn lint (passed)

@wanxiankai

Copy link
Copy Markdown
Author

CI follow-up: the s2-docs job exited with status 129 (SIGHUP) while Parcel was building, without a compiler or validation error. I ran the same make s2-docs command locally on the current head bf9399e72; it completed successfully, including validation of all 500 generated HTML files. All other CircleCI jobs have passed, so this appears to be a transient runner interruption. Could a maintainer please rerun s2-docs?

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.

Select does not propagate change event

2 participants