Improve HTML form integration#4640
Conversation
Also make sure mobile combo box actually submits a value to a form. #1690
…gration # Conflicts: # packages/@react-spectrum/combobox/src/ComboBox.tsx # packages/react-aria-components/src/Select.tsx
|
Build successful! 🎉 |
|
## API Changes
unknown top level export { type: 'identifier', name: 'Column' } @react-aria/checkboxAriaCheckboxGroupItemProps AriaCheckboxGroupItemProps {
aria-controls?: string
children?: ReactNode
isIndeterminate?: boolean
- name?: string
onChange?: (boolean) => void
value: string
}AriaCheckboxGroupProps AriaCheckboxGroupProps {
- name?: string
+
}AriaCheckboxProps AriaCheckboxProps {
aria-controls?: string
children?: ReactNode
defaultSelected?: boolean
isIndeterminate?: boolean
isSelected?: boolean
- name?: string
onChange?: (boolean) => void
value?: string
}@react-aria/comboboxAriaComboBoxProps AriaComboBoxProps<T> {
allowsCustomValue?: boolean
defaultInputValue?: string
defaultItems?: Iterable<T>
inputValue?: string
items?: Iterable<T>
menuTrigger?: MenuTriggerAction = 'input'
- name?: string
onInputChange?: (string) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
shouldFocusWrap?: boolean
}@react-aria/datepickeruseTimeFieldchanged by:
useTimeField<T extends TimeValue> {
- props: AriaTimeFieldProps<T>
- state: DateFieldState
+ props: AriaTimeFieldOptions<T>
+ state: TimeFieldState
ref: RefObject<Element>
returnVal: undefined
}AriaDateRangePickerProps AriaDateRangePickerProps<T extends DateValue> {
allowsNonContiguousRanges?: boolean
+ endName?: string
granularity?: Granularity
hideTimeZone?: boolean = false
hourCycle?: number | number
isDateUnavailable?: (DateValue) => boolean
maxValue?: DateValue
minValue?: DateValue
placeholderValue?: DateValue
+ startName?: string
}AriaDateFieldOptions AriaDateFieldOptions<T extends DateValue> {
-
+ inputRef?: RefObject<HTMLInputElement>
}it changed:
DateFieldAria DateFieldAria {
descriptionProps: DOMAttributes
errorMessageProps: DOMAttributes
fieldProps: DOMAttributes
+ inputProps: InputHTMLAttributes<HTMLInputElement>
labelProps: DOMAttributes
}it changed:
@react-aria/radioAriaRadioGroupProps AriaRadioGroupProps {
- name?: string
orientation?: Orientation = 'vertical'
}RadioAria RadioAria {
inputProps: InputHTMLAttributes<HTMLInputElement>
isDisabled: boolean
isPressed: boolean
isSelected: boolean
+ validationState: ValidationState
}it changed:
@react-aria/selectuseHiddenSelect useHiddenSelect<T> {
- props: AriaHiddenSelectProps
+ props: AriaHiddenSelectOptions
state: SelectState<T>
triggerRef: RefObject<FocusableElement>
returnVal: undefined
}AriaSelectOptions AriaSelectOptions<T> {
+ hiddenSelectRef?: RefObject<HTMLSelectElement>
keyboardDelegate?: KeyboardDelegate
}it changed:
@react-aria/switchAriaSwitchProps AriaSwitchProps {
aria-controls?: string
children?: ReactNode
defaultSelected?: boolean
isSelected?: boolean
- name?: string
onChange?: (boolean) => void
value?: string
}@react-aria/toggleAriaToggleProps AriaToggleProps {
aria-controls?: string
children?: ReactNode
defaultSelected?: boolean
isSelected?: boolean
- name?: string
onChange?: (boolean) => void
value?: string
}@react-aria/utilsuseDeepMemo-
+useFormReset<T> {
+ ref: RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
+ initialValue: T
+ onReset: (T) => void
+ returnVal: undefined
+}useFormReset-
+useFormValidation {
+ ref: RefObject<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
+ validationState: ValidationState
+ errorMessage: ReactNode
+ validationBehavior: 'native' | 'aria'
+ onValidationChange?: (FormValidationEvent) => void
+ returnVal: undefined
+}useFormValidation-
+useFormValidationState {
+ validationState: ValidationState
+ errorMessage: ReactNode
+ returnVal: undefined
+}useFormValidationState-
+mergeValidity {
+ a: ValidityState
+ b: ValidityState
+ returnVal: undefined
+}mergeValidity-
+FormValidationResult {
+ errorMessage: ReactNode
+ validationDetails: ValidityState
+ validationState: ValidationState
+}@react-spectrum/checkboxCheckbox SpectrumCheckboxProps {
aria-controls?: string
children?: ReactNode
defaultSelected?: boolean
isEmphasized?: boolean
isIndeterminate?: boolean
isSelected?: boolean
- name?: string
onChange?: (boolean) => void
value?: string
}CheckboxGroup SpectrumCheckboxGroupProps {
children: ReactElement<CheckboxProps> | Array<ReactElement<CheckboxProps>>
isEmphasized?: boolean
- name?: string
orientation?: Orientation = 'vertical'
}@react-spectrum/colorColorArea SpectrumColorAreaProps {
isDisabled?: boolean
onChange?: (Color) => void
onChangeEnd?: (Color) => void
size?: DimensionValue
xChannel?: ColorChannel
+ xName?: string
yChannel?: ColorChannel
+ yName?: string
}@react-spectrum/comboboxSection SpectrumComboBoxProps<T> {
allowsCustomValue?: boolean
defaultInputValue?: string
defaultItems?: Iterable<T>
direction?: 'bottom' | 'top' = 'bottom'
+ formValue?: 'text' | 'key' = 'text'
inputValue?: string
isQuiet?: boolean
items?: Iterable<T>
loadingState?: LoadingState
menuTrigger?: MenuTriggerAction = 'input'
- name?: string
onInputChange?: (string) => void
onOpenChange?: (boolean, MenuTriggerAction) => void
shouldFlip?: boolean = true
shouldFocusWrap?: boolean
@react-spectrum/datepickerTimeField SpectrumDateRangePickerProps<T extends DateValue> {
allowsNonContiguousRanges?: boolean
+ endName?: string
granularity?: Granularity
hideTimeZone?: boolean = false
hourCycle?: number | number
isDateUnavailable?: (DateValue) => boolean
isQuiet?: boolean = false
maxValue?: DateValue
maxVisibleMonths?: number = 1
minValue?: DateValue
placeholderValue?: DateValue
shouldFlip?: boolean = true
showFormatHelpText?: boolean = false
+ startName?: string
}@react-spectrum/formForm SpectrumFormProps {
action?: string
children: ReactElement<SpectrumLabelableProps> | Array<ReactElement<SpectrumLabelableProps>>
encType?: 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/plain'
isDisabled?: boolean
isEmphasized?: boolean
isQuiet?: boolean
isReadOnly?: boolean
isRequired?: boolean
method?: 'get' | 'post'
onSubmit?: FormEventHandler
target?: '_blank' | '_self' | '_parent' | '_top'
+ validationBehavior?: 'aria' | 'native'
validationState?: ValidationState = 'valid'
}@react-spectrum/radioRadioGroup SpectrumRadioGroupProps {
children: ReactElement<RadioProps> | Array<ReactElement<RadioProps>>
isEmphasized?: boolean
- name?: string
orientation?: Orientation = 'vertical'
}@react-spectrum/sliderSlider SpectrumRangeSliderProps {
contextualHelp?: ReactNode
+ endName?: string
formatOptions?: Intl.NumberFormatOptions
getValueLabel?: (RangeValue<number>) => string
isDisabled?: boolean
labelPosition?: LabelPosition = 'top'
maxValue?: number = 100
minValue?: number = 0
onChangeEnd?: (RangeValue<number>) => void
orientation?: Orientation = 'horizontal'
showValueLabel?: boolean
+ startName?: string
step?: number = 1
}@react-spectrum/switchSwitch SpectrumSwitchProps {
aria-controls?: string
children?: ReactNode
defaultSelected?: boolean
isEmphasized?: boolean
isSelected?: boolean
- name?: string
onChange?: (boolean) => void
value?: string
}@react-stately/datepickerTimeFieldState-
+TimeFieldState {
+ timeValue: Time
+}it changed:
@react-stately/numberfieldNumberFieldState NumberFieldState {
canDecrement: boolean
canIncrement: boolean
commit: () => void
decrement: () => void
decrementToMin: () => void
increment: () => void
incrementToMax: () => void
inputValue: string
maxValue: number
minValue: number
numberValue: number
setInputValue: (string) => void
+ setNumberValue: (number) => void
validate: (string) => boolean
}it changed:
@react-stately/radioRadioGroupProps RadioGroupProps {
- name?: string
orientation?: Orientation = 'vertical'
}@react-stately/toggleToggleProps ToggleProps {
children?: ReactNode
defaultSelected?: boolean
isSelected?: boolean
- name?: string
onChange?: (boolean) => void
value?: string
} |
| * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
| * OF ANY KIND, either express or implied. See the License for the specific language | ||
| * governing permissions and limitations under the License. | ||
| */ |
There was a problem hiding this comment.
I put this in @react-aria/utils for now. Wondering if we should make a new package like @react-aria/form for it though. I thought utils since it wasn't really meant as public API, but there is a lot here. 🤷
There was a problem hiding this comment.
yes, i think it should be in a form package, even if it's not public right now, we may want it to be eventually. we do that in other places, we just don't document the things in it and we don't mention the package on our docs website.
If we wanted, we could start putting in the Readme that these are intended as private as well
|
Apologies if this is rude of me, but can I ask why this PR is still in draft? Is there any work missing to get this merged? And if so, could I maybe help with anything? |
|
@romansndlr it is sorta experimental at the moment, and needs a bunch more API refinement. For example I was working on a way to also support server side validation errors along with client side ones. After we figure that out the docs need to be written and we need to test everything out in real scenarios. I'd say it's a little ways off unfortunately. Glad it's interesting to you though! |
|
@devongovett That's awesome man and the work y'all are doing here is unbelievable! I was wondering if maybe there is a way to move forward with just the native HTML form integration part? We are working on a Remix app and I currently manage the "name" prop myself for the |
|
@romansndlr I extracted the |
That is amazing dude!! Thank you so much 🙏🏻 |
|
@devongovett Thank you for the amazing work y'all doing, I love react-spectrum! I'm using
Do you mind showing me a very simple example of how and where do I put this extra hidden input and what props or properties should have to make the name property work? Thank you :) |
|
A good place to go for examples is our React Aria Components If you can, we suggest using RAC because we'll handle putting all the requisite hooks together for you but still leave the dom up to you. |
Thank you so much Robert for the quick reply! Really appreciate the example, I'll incorporate the RAC later on :) |
Closes #1690, closes #2825
This improves HTML form integration across all of our field components. It allows you to implement custom UI for native browser validation rather than implementing custom validation in JS. This is often a lot easier than dealing with controlled form state. It also improves integration with upcoming React features like server actions.
<Button type="reset">. This is implemented by subscribing to theresetevent on the input's parent form, and triggering an onChange with the initially rendered value. Works with both uncontrolled and controlled inputs.nameprop wasn't passed through at all, so this is not a breaking change. It is implemented by rendering an extra<input type="hidden">.nameprop to slider and color components.<input type="hidden">.<input type="hidden">. This can be controlled by setting theformValueprop. WhenallowsCustomValueis true, we always submit the text value.validationBehaviorprop, which can be set to either "aria" or "native". When set to "native", we use the HTMLrequiredprop rather thanaria-required, which blocks form submission.validationState,errorMessage, andvalidationDetailsas returned properties from all field aria hooks. WhenvalidationBehavior="native", we handle the native input "invalid" event and prevent default on it so that the browser's default form validation UI does not appear. Then we return the validation state and native error message from the hook so it can be rendered in a custom UI. In Spectrum, this is implemented as help text.validationBehavior="native", and an explicitvalidationState="invalid"prop is set, this state is also set on the underlying native input element (viasetCustomValidity) so that form submission is blocked.onValidationChangeevent is also added to allow implementing more custom behavior when fields become valid or invalid.<FormError>component is added, which allows you to use render props to render a custom form error message based on validation details from the input. We also setvalidationBehavior="native"there by default. In our hooks and spectrum this would be a breaking change so you must opt-in.To do