Skip to content

Commit 0ee6a00

Browse files
authored
Add docs for form validation (#5343)
1 parent 919281f commit 0ee6a00

File tree

46 files changed

+3156
-1001
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3156
-1001
lines changed

packages/@react-aria/datepicker/src/useDatePicker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
164164
isDateUnavailable: props.isDateUnavailable,
165165
defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue,
166166
isInvalid: state.isInvalid,
167-
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : props.errorMessage
167+
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : (props.errorMessage || state.displayValidation.validationErrors.join(' '))
168168
},
169169
isInvalid,
170170
validationErrors,

packages/@react-aria/datepicker/src/useDateRangePicker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
213213
allowsNonContiguousRanges: props.allowsNonContiguousRanges,
214214
defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue,
215215
isInvalid: state.isInvalid,
216-
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : props.errorMessage
216+
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : (props.errorMessage || state.displayValidation.validationErrors.join(' '))
217217
},
218218
isInvalid,
219219
validationErrors,

packages/@react-aria/textfield/src/useTextField.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ import {
1717
HTMLAttributes,
1818
LabelHTMLAttributes,
1919
ReactDOM,
20-
RefObject
20+
RefObject,
21+
useEffect
2122
} from 'react';
2223
import {DOMAttributes, ValidationResult} from '@react-types/shared';
23-
import {filterDOMProps, mergeProps, useFormReset} from '@react-aria/utils';
24+
import {filterDOMProps, getOwnerWindow, mergeProps, useFormReset} from '@react-aria/utils';
2425
import {useControlledState} from '@react-stately/utils';
2526
import {useField} from '@react-aria/label';
2627
import {useFocusable} from '@react-aria/focus';
@@ -138,6 +139,24 @@ export function useTextField<T extends TextFieldIntrinsicElements = DefaultEleme
138139
useFormReset(ref, value, setValue);
139140
useFormValidation(props, validationState, ref);
140141

142+
useEffect(() => {
143+
// This works around a React/Chrome bug that prevents textarea elements from validating when controlled.
144+
// We prevent React from updating defaultValue (i.e. children) of textarea when `value` changes,
145+
// which causes Chrome to skip validation. Only updating `value` is ok in our case since our
146+
// textareas are always controlled. React is planning on removing this synchronization in a
147+
// future major version.
148+
// https://github.com/facebook/react/issues/19474
149+
// https://github.com/facebook/react/issues/11896
150+
if (ref.current instanceof getOwnerWindow(ref.current).HTMLTextAreaElement) {
151+
let input = ref.current;
152+
Object.defineProperty(input, 'defaultValue', {
153+
get: () => input.value,
154+
set: () => {},
155+
configurable: true
156+
});
157+
}
158+
}, [ref]);
159+
141160
return {
142161
labelProps,
143162
inputProps: mergeProps(

packages/@react-spectrum/autocomplete/docs/SearchAutocomplete.mdx

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -266,41 +266,34 @@ function AsyncLoadingExample() {
266266
```
267267

268268
## Validation
269-
SearchAutocomplete can display a validation state to communicate to the user whether the current value is valid or invalid.
270-
Implement your own validation logic in your app and pass either `"valid"` or `"invalid"` to the SearchAutocomplete via the `validationState` prop.
271269

272-
The example below illustrates how one would validate if the user has entered a valid email into the SearchAutocomplete.
273-
```tsx example
274-
function Example() {
275-
let [value, setValue] = React.useState('[email protected]');
276-
let isValid = React.useMemo(() => /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value), [value]);
270+
SearchAutocomplete supports the `isRequired` prop to ensure the user enters a value, as well as custom client and server-side validation. It can also be integrated with other form libraries. See the [Forms](forms.html) guide to learn more.
277271

278-
let options = [
279-
{id: 1, email: '[email protected]'},
280-
{id: 2, email: '[email protected]'},
281-
{id: 3, email: '[email protected]'},
282-
{id: 4, email: '[email protected]'},
283-
{id: 5, email: '[email protected]'},
284-
{id: 6, email: '[email protected]'},
285-
{id: 7, email: '[email protected]'},
286-
{id: 8, email: '[email protected]'},
287-
{id: 9, email: '[email protected]'}
288-
];
272+
When the [Form](Form.html) component has the `validationBehavior="native"` prop, validation errors block form submission and are displayed as help text automatically. Errors are displayed when the user blurs the search field or submits the form.
289273

290-
return (
291-
<SearchAutocomplete
292-
width="size-3000"
293-
label="Search Email Addresses"
294-
validationState={isValid ? 'valid' : 'invalid'}
295-
defaultItems={options}
296-
inputValue={value}
297-
onInputChange={setValue}>
298-
{item => <Item>{item.email}</Item>}
299-
</SearchAutocomplete>
300-
);
301-
}
274+
```tsx example
275+
import {Form, ButtonGroup, Button} from '@adobe/react-spectrum';
276+
277+
<Form validationBehavior="native" maxWidth="size-3000">
278+
{/*- begin highlight -*/}
279+
<SearchAutocomplete label="Favorite animal" name="animal" isRequired>
280+
{/*- end highlight -*/}
281+
<Item>Aardvark</Item>
282+
<Item>Cat</Item>
283+
<Item>Dog</Item>
284+
<Item>Kangaroo</Item>
285+
<Item>Panda</Item>
286+
<Item>Snake</Item>
287+
</SearchAutocomplete>
288+
<ButtonGroup>
289+
<Button type="submit" variant="primary">Submit</Button>
290+
<Button type="reset" variant="secondary">Reset</Button>
291+
</ButtonGroup>
292+
</Form>
302293
```
303294

295+
By default, `SearchAutocomplete` displays default validation messages provided by the browser. See [Customizing error messages](forms.html#customizing-error-messages) in the Forms guide to learn how to provide your own custom errors.
296+
304297
## Custom Filtering
305298
By default, SearchAutocomplete uses a string "contains" filtering strategy when deciding what items to display in the dropdown menu. This filtering strategy can be overwritten
306299
by filtering the list of items yourself and passing the filtered list to the SearchAutocomplete via the `items` prop.

packages/@react-spectrum/checkbox/docs/CheckboxGroup.mdx

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -156,54 +156,51 @@ function Example() {
156156

157157
## Validation
158158

159-
CheckboxGroups can display a validation state to communicate to the user if the current value is invalid.
160-
Implement your own validation logic in your app and set the `isInvalid` prop to either
161-
the `CheckboxGroup` or an individual `Checkbox` to mark it as invalid.
159+
CheckboxGroup supports the `isRequired` prop to ensure the user selects at least one item, as well as custom client and server-side validation. Individual checkboxes also support validation, and errors from all checkboxes are aggregated at the group level. CheckboxGroup can also be integrated with other form libraries. See the [Forms](forms.html) guide to learn more.
162160

163161
### Group validation
164162

165-
If the group as a whole is invalid, for example if the user must choose at least one option but failed
166-
to do so, then pass `isInvalid` to the `CheckboxGroup`.
167-
168-
The following example shows how to require that at least one option is selected. It sets the `isInvalid`
169-
prop when no options are selected and removes it otherwise.
163+
The `isRequired` prop at the `CheckboxGroup` level requires that at least one item is selected. When the [Form](Form.html) component has the `validationBehavior="native"` prop, validation errors block form submission and are displayed as help text automatically.
170164

171165
```tsx example
172-
function Example() {
173-
let [selected, setSelected] = React.useState([]);
174-
175-
return (
176-
<CheckboxGroup label="Sandwich condiments" value={selected} onChange={setSelected} isRequired isInvalid={selected.length === 0}>
177-
<Checkbox value="lettuce">Lettuce</Checkbox>
178-
<Checkbox value="tomato">Tomato</Checkbox>
179-
<Checkbox value="onion">Onion</Checkbox>
180-
<Checkbox value="sprouts">Sprouts</Checkbox>
181-
</CheckboxGroup>
182-
);
183-
}
166+
import {Form, ButtonGroup, Button} from '@adobe/react-spectrum';
167+
168+
<Form validationBehavior="native">
169+
{/*- begin highlight -*/}
170+
<CheckboxGroup label="Sandwich condiments" name="condiments" isRequired>
171+
{/*- end highlight -*/}
172+
<Checkbox value="lettuce">Lettuce</Checkbox>
173+
<Checkbox value="tomato">Tomato</Checkbox>
174+
<Checkbox value="onion">Onion</Checkbox>
175+
<Checkbox value="sprouts">Sprouts</Checkbox>
176+
</CheckboxGroup>
177+
<ButtonGroup>
178+
<Button type="submit" variant="primary">Submit</Button>
179+
<Button type="reset" variant="secondary">Reset</Button>
180+
</ButtonGroup>
181+
</Form>
184182
```
185183

186-
### Individual Checkbox validation
184+
By default, `CheckboxGroup` displays default validation messages provided by the browser. See [Customizing error messages](forms.html#customizing-error-messages) in the Forms guide to learn how to provide your own custom errors.
187185

188-
If an individual checkbox is invalid, for example if the user must select a particular option but failed
189-
to do so, then pass `isInvalid` to the `Checkbox` element instead.
186+
### Individual Checkbox validation
190187

191-
The following example shows how to require that all items are selected. It uses the `isRequired` prop on each individual `Checkbox`
192-
element to indicate to assistive technology that every checkbox is required. By default, the `isRequired` prop on the `CheckboxGroup` only
193-
indicates that the group is required, not any individual option. In addition, `isInvalid` is set on each checkbox that is not yet checked.
188+
To require that specific checkboxes are checked, set the `isRequired` prop at the `Checkbox` level instead of the `CheckboxGroup`. The following example shows how to require that all items are selected.
194189

195190
```tsx example
196-
function Example() {
197-
let [selected, setSelected] = React.useState([]);
198-
199-
return (
200-
<CheckboxGroup label="Agree to the following" isRequired value={selected} onChange={setSelected}>
201-
<Checkbox value="terms" isRequired isInvalid={!selected.includes('terms')}>Terms and conditions</Checkbox>
202-
<Checkbox value="privacy" isRequired isInvalid={!selected.includes('privacy')}>Privacy policy</Checkbox>
203-
<Checkbox value="cookies" isRequired isInvalid={!selected.includes('cookies')}>Cookie policy</Checkbox>
204-
</CheckboxGroup>
205-
);
206-
}
191+
<Form validationBehavior="native">
192+
<CheckboxGroup label="Agree to the following" isRequired>
193+
{/*- begin highlight -*/}
194+
<Checkbox value="terms" isRequired>Terms and conditions</Checkbox>
195+
<Checkbox value="privacy" isRequired>Privacy policy</Checkbox>
196+
<Checkbox value="cookies" isRequired>Cookie policy</Checkbox>
197+
{/*- end highlight -*/}
198+
</CheckboxGroup>
199+
<ButtonGroup>
200+
<Button type="submit" variant="primary">Submit</Button>
201+
<Button type="reset" variant="secondary">Reset</Button>
202+
</ButtonGroup>
203+
</Form>
207204
```
208205

209206
## Props

packages/@react-spectrum/color/docs/ColorField.mdx

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -103,31 +103,26 @@ When the `necessityIndicator` prop is set to `"label"`, a localized string will
103103

104104
## Validation
105105

106-
ColorField can display a validation state to communicate to the user whether the current value is valid or invalid.
107-
Implement your own validation logic in your app and pass either `"valid"` or `"invalid"` to the ColorField via the `validationState` prop.
106+
ColorField supports the `isRequired` prop to ensure the user enters a value, as well as custom validation functions, realtime validation, and server-side validation. It can also be integrated with other form libraries. See the [Forms](forms.html) guide to learn more.
108107

109-
The example below illustrates how one would validate if the user has entered a red color. In it,
110-
the <TypeLink links={statelyDocs.links} type={statelyDocs.exports.parseColor} /> function is used to parse the
111-
initial color from a hexadecimal string so that `value`'s type remains consistent.
108+
When the [Form](Form.html) component has the `validationBehavior="native"` prop, validation errors block form submission and are displayed as help text automatically. Errors are displayed when the user blurs the color field or submits the form.
112109

113110
```tsx example
114-
function Example() {
115-
let [value, setValue] = React.useState(parseColor('#e73623'));
116-
let isValid = React.useMemo(() => {
117-
return value && value.getChannelValue('red') > value.getChannelValue('green') && value.getChannelValue('red') > value.getChannelValue('blue');
118-
}, [value]);
119-
120-
return (
121-
<ColorField
122-
validationState={isValid ? 'valid' : 'invalid'}
123-
value={value}
124-
onChange={setValue}
125-
label="Red colors"
126-
/>
127-
);
128-
}
111+
import {Form, ButtonGroup, Button} from '@adobe/react-spectrum';
112+
113+
<Form validationBehavior="native" maxWidth="size-3000">
114+
{/*- begin highlight -*/}
115+
<ColorField label="Color" name="color" isRequired />
116+
{/*- end highlight -*/}
117+
<ButtonGroup>
118+
<Button type="submit" variant="primary">Submit</Button>
119+
<Button type="reset" variant="secondary">Reset</Button>
120+
</ButtonGroup>
121+
</Form>
129122
```
130123

124+
By default, `ColorField` displays default validation messages provided by the browser. See [Customizing error messages](forms.html#customizing-error-messages) in the Forms guide to learn how to provide your own custom errors.
125+
131126
## Props
132127

133128
<PropTable component={docs.exports.ColorField} links={docs.links} />
@@ -172,23 +167,10 @@ left most edge of the ColorField and "end" refers to the right most edge. For ri
172167
Both a description and an error message can be supplied to a ColorField. The description is always visible unless the `validationState` is “invalid” and an error message is provided. The error message can be used to help the user fix their input quickly and should be specific to the detected error. All strings should be localized.
173168

174169
```tsx example
175-
function Example() {
176-
let [value, setValue] = React.useState(parseColor('#e73623'));
177-
let isValid = React.useMemo(() => {
178-
return value && value.getChannelValue('red') > value.getChannelValue('green') && value.getChannelValue('red') > value.getChannelValue('blue');
179-
}, [value]);
180-
181-
return (
182-
<ColorField
183-
validationState={isValid ? 'valid' : 'invalid'}
184-
value={value}
185-
onChange={setValue}
186-
label="Red colors"
187-
description="Enter a red color."
188-
errorMessage={value === null ? 'Empty input not allowed.' : 'Not a red color.'}
189-
/>
190-
);
191-
}
170+
<Flex gap="size-100" wrap>
171+
<ColorField label="Color" defaultValue="#abc" validationState="valid" description="Enter your favorite color." />
172+
<ColorField label="Color" validationState="invalid" errorMessage="Empty input is not allowed." />
173+
</Flex>
192174
```
193175

194176
### Contextual help

packages/@react-spectrum/combobox/docs/ComboBox.mdx

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -659,42 +659,34 @@ function AsyncLoadingExample() {
659659
```
660660

661661
## Validation
662-
ComboBox can display a validation state to communicate to the user whether the current value is valid or invalid.
663-
Implement your own validation logic in your app and pass either `"valid"` or `"invalid"` to the ComboBox via the `validationState` prop.
664662

665-
The example below illustrates how one would validate if the user has entered a valid email into the ComboBox.
666-
```tsx example
667-
function Example() {
668-
let [value, setValue] = React.useState('[email protected]');
669-
let isValid = React.useMemo(() => /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value), [value]);
663+
ComboBox supports the `isRequired` prop to ensure the user enters a value, as well as custom client and server-side validation. It can also be integrated with other form libraries. See the [Forms](forms.html) guide to learn more.
670664

671-
let options = [
672-
{id: 1, email: '[email protected]'},
673-
{id: 2, email: '[email protected]'},
674-
{id: 3, email: '[email protected]'},
675-
{id: 4, email: '[email protected]'},
676-
{id: 5, email: '[email protected]'},
677-
{id: 6, email: '[email protected]'},
678-
{id: 7, email: '[email protected]'},
679-
{id: 8, email: '[email protected]'},
680-
{id: 9, email: '[email protected]'}
681-
];
665+
When the [Form](Form.html) component has the `validationBehavior="native"` prop, validation errors block form submission and are displayed as help text automatically. Errors are displayed when the user blurs the combo box or submits the form.
682666

683-
return (
684-
<ComboBox
685-
width="size-3000"
686-
label="To:"
687-
validationState={isValid ? 'valid' : 'invalid'}
688-
defaultItems={options}
689-
inputValue={value}
690-
onInputChange={setValue}
691-
allowsCustomValue>
692-
{item => <Item>{item.email}</Item>}
693-
</ComboBox>
694-
);
695-
}
667+
```tsx example
668+
import {Form, ButtonGroup, Button} from '@adobe/react-spectrum';
669+
670+
<Form validationBehavior="native" maxWidth="size-3000">
671+
{/*- begin highlight -*/}
672+
<ComboBox label="Favorite animal" name="animal" isRequired>
673+
{/*- end highlight -*/}
674+
<Item>Aardvark</Item>
675+
<Item>Cat</Item>
676+
<Item>Dog</Item>
677+
<Item>Kangaroo</Item>
678+
<Item>Panda</Item>
679+
<Item>Snake</Item>
680+
</ComboBox>
681+
<ButtonGroup>
682+
<Button type="submit" variant="primary">Submit</Button>
683+
<Button type="reset" variant="secondary">Reset</Button>
684+
</ButtonGroup>
685+
</Form>
696686
```
697687

688+
By default, `ComboBox` displays default validation messages provided by the browser. See [Customizing error messages](forms.html#customizing-error-messages) in the Forms guide to learn how to provide your own custom errors.
689+
698690
## Custom Filtering
699691
By default, ComboBox uses a string "contains" filtering strategy when deciding what items to display in the dropdown menu. This filtering strategy can be overwritten
700692
by filtering the list of items yourself and passing the filtered list to the ComboBox via the `items` prop.

0 commit comments

Comments
 (0)