From 62cdb87b02e36605623308856f5c5c642c61ea52 Mon Sep 17 00:00:00 2001 From: Lucas Weng Date: Thu, 22 May 2025 06:42:00 +0800 Subject: [PATCH 1/5] fix: prevent form submission when required Select has more than 300 options and no selection --- .../@react-aria/select/src/HiddenSelect.tsx | 10 +++++-- .../stories/Select.stories.tsx | 28 ++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/packages/@react-aria/select/src/HiddenSelect.tsx b/packages/@react-aria/select/src/HiddenSelect.tsx index b0d61ddc2aa..76b0b884c8a 100644 --- a/packages/@react-aria/select/src/HiddenSelect.tsx +++ b/packages/@react-aria/select/src/HiddenSelect.tsx @@ -141,13 +141,19 @@ export function HiddenSelect(props: HiddenSelectProps): JSX.Element | null ); } else if (name) { + let data = selectData.get(state) || {}; + // Use a hidden rather than + // so that an empty value blocks HTML form submission when the field is required. return ( + value={state.selectedKey ?? ''} + onChange={() => {/** Ignore react warning. */}} + required={data.isRequired} + hidden /> ); } diff --git a/packages/react-aria-components/stories/Select.stories.tsx b/packages/react-aria-components/stories/Select.stories.tsx index 51f9473833a..b2018191e80 100644 --- a/packages/react-aria-components/stories/Select.stories.tsx +++ b/packages/react-aria-components/stories/Select.stories.tsx @@ -64,7 +64,11 @@ export const SelectRenderProps = () => ( ); -let manyItems = [...Array(100)].map((_, i) => ({id: i, name: `Item ${i}`})); +let makeItems = (length: number) => Array.from({length}, (_, i) => ({ + id: i, + name: `Item ${i}` +})); +let manyItems = makeItems(100); export const SelectManyItems = () => ( + + + + + {item => {item.name}} + + + + + +); From 8f2dc84028f1eb9be2467392e68b0ac5ab9b8a75 Mon Sep 17 00:00:00 2001 From: Lucas Weng Date: Thu, 29 May 2025 08:28:04 +0800 Subject: [PATCH 2/5] fix: only block form submission if the validation behavior is native --- .../@react-aria/select/src/HiddenSelect.tsx | 36 ++++++++++++------- .../stories/Select.stories.tsx | 12 +++++-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/packages/@react-aria/select/src/HiddenSelect.tsx b/packages/@react-aria/select/src/HiddenSelect.tsx index 76b0b884c8a..bbff8b81184 100644 --- a/packages/@react-aria/select/src/HiddenSelect.tsx +++ b/packages/@react-aria/select/src/HiddenSelect.tsx @@ -11,7 +11,7 @@ */ import {FocusableElement, RefObject} from '@react-types/shared'; -import React, {JSX, ReactNode, useRef} from 'react'; +import React, {InputHTMLAttributes, JSX, ReactNode, useRef} from 'react'; import {selectData} from './useSelect'; import {SelectState} from '@react-stately/select'; import {useFormReset} from '@react-aria/utils'; @@ -142,19 +142,29 @@ export function HiddenSelect(props: HiddenSelectProps): JSX.Element | null ); } else if (name) { let data = selectData.get(state) || {}; - // Use a hidden rather than - // so that an empty value blocks HTML form submission when the field is required. + let {validationBehavior} = data; + + let inputProps: InputHTMLAttributes = { + type: 'hidden', + autoComplete: selectProps.autoComplete, + name, + disabled: isDisabled, + value: state.selectedKey ?? '' + }; + + if (validationBehavior === "native") { + // Use a hidden rather than + // so that an empty value blocks HTML form submission when the field is required. + inputProps.type = 'text'; + inputProps.hidden = true; + inputProps.required = selectProps.required; + // Ignore react warning. + inputProps.onChange = () => {}; + } + return ( - {/** Ignore react warning. */}} - required={data.isRequired} - hidden /> - ); + + ) } return null; diff --git a/packages/react-aria-components/stories/Select.stories.tsx b/packages/react-aria-components/stories/Select.stories.tsx index b2018191e80..d2f19fa601d 100644 --- a/packages/react-aria-components/stories/Select.stories.tsx +++ b/packages/react-aria-components/stories/Select.stories.tsx @@ -18,7 +18,13 @@ import {UNSTABLE_ListBoxLoadingSentinel} from '../src/ListBox'; import {useAsyncList} from 'react-stately'; export default { - title: 'React Aria Components' + title: 'React Aria Components', + argTypes: { + validationBehavior: { + control: 'select', + options: ['native', 'aria'] + } + } }; export const SelectExample = () => ( @@ -183,9 +189,9 @@ AsyncVirtualizedCollectionRenderSelect.story = { // Required select validation cannot currently be tested in the jsdom environment. // In jsdom, forms are submitted even when required fields are empty. // See: https://github.com/jsdom/jsdom/issues/2898 -export const RequiredSelectWithManyItems = () => ( +export const RequiredSelectWithManyItems = (props) => (
-