Skip to content

Commit

Permalink
Add docs for form validation (#5343)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett authored Nov 3, 2023
1 parent 919281f commit 0ee6a00
Show file tree
Hide file tree
Showing 46 changed files with 3,156 additions and 1,001 deletions.
2 changes: 1 addition & 1 deletion packages/@react-aria/datepicker/src/useDatePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export function useDatePicker<T extends DateValue>(props: AriaDatePickerProps<T>
isDateUnavailable: props.isDateUnavailable,
defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue,
isInvalid: state.isInvalid,
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : props.errorMessage
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : (props.errorMessage || state.displayValidation.validationErrors.join(' '))
},
isInvalid,
validationErrors,
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/datepicker/src/useDateRangePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export function useDateRangePicker<T extends DateValue>(props: AriaDateRangePick
allowsNonContiguousRanges: props.allowsNonContiguousRanges,
defaultFocusedValue: state.dateRange ? undefined : props.placeholderValue,
isInvalid: state.isInvalid,
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : props.errorMessage
errorMessage: typeof props.errorMessage === 'function' ? props.errorMessage(state.displayValidation) : (props.errorMessage || state.displayValidation.validationErrors.join(' '))
},
isInvalid,
validationErrors,
Expand Down
23 changes: 21 additions & 2 deletions packages/@react-aria/textfield/src/useTextField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import {
HTMLAttributes,
LabelHTMLAttributes,
ReactDOM,
RefObject
RefObject,
useEffect
} from 'react';
import {DOMAttributes, ValidationResult} from '@react-types/shared';
import {filterDOMProps, mergeProps, useFormReset} from '@react-aria/utils';
import {filterDOMProps, getOwnerWindow, mergeProps, useFormReset} from '@react-aria/utils';
import {useControlledState} from '@react-stately/utils';
import {useField} from '@react-aria/label';
import {useFocusable} from '@react-aria/focus';
Expand Down Expand Up @@ -138,6 +139,24 @@ export function useTextField<T extends TextFieldIntrinsicElements = DefaultEleme
useFormReset(ref, value, setValue);
useFormValidation(props, validationState, ref);

useEffect(() => {
// This works around a React/Chrome bug that prevents textarea elements from validating when controlled.
// We prevent React from updating defaultValue (i.e. children) of textarea when `value` changes,
// which causes Chrome to skip validation. Only updating `value` is ok in our case since our
// textareas are always controlled. React is planning on removing this synchronization in a
// future major version.
// https://github.com/facebook/react/issues/19474
// https://github.com/facebook/react/issues/11896
if (ref.current instanceof getOwnerWindow(ref.current).HTMLTextAreaElement) {
let input = ref.current;
Object.defineProperty(input, 'defaultValue', {
get: () => input.value,
set: () => {},
configurable: true
});
}
}, [ref]);

return {
labelProps,
inputProps: mergeProps(
Expand Down
53 changes: 23 additions & 30 deletions packages/@react-spectrum/autocomplete/docs/SearchAutocomplete.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -266,41 +266,34 @@ function AsyncLoadingExample() {
```

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

The example below illustrates how one would validate if the user has entered a valid email into the SearchAutocomplete.
```tsx example
function Example() {
let [value, setValue] = React.useState('me@email.com');
let isValid = React.useMemo(() => /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value), [value]);
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.

let options = [
{id: 1, email: 'fake@email.com'},
{id: 2, email: 'anotherfake@email.com'},
{id: 3, email: 'bob@email.com'},
{id: 4, email: 'joe@email.com'},
{id: 5, email: 'yourEmail@email.com'},
{id: 6, email: 'valid@email.com'},
{id: 7, email: 'spam@email.com'},
{id: 8, email: 'newsletter@email.com'},
{id: 9, email: 'subscribe@email.com'}
];
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.

return (
<SearchAutocomplete
width="size-3000"
label="Search Email Addresses"
validationState={isValid ? 'valid' : 'invalid'}
defaultItems={options}
inputValue={value}
onInputChange={setValue}>
{item => <Item>{item.email}</Item>}
</SearchAutocomplete>
);
}
```tsx example
import {Form, ButtonGroup, Button} from '@adobe/react-spectrum';

<Form validationBehavior="native" maxWidth="size-3000">
{/*- begin highlight -*/}
<SearchAutocomplete label="Favorite animal" name="animal" isRequired>
{/*- end highlight -*/}
<Item>Aardvark</Item>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>Kangaroo</Item>
<Item>Panda</Item>
<Item>Snake</Item>
</SearchAutocomplete>
<ButtonGroup>
<Button type="submit" variant="primary">Submit</Button>
<Button type="reset" variant="secondary">Reset</Button>
</ButtonGroup>
</Form>
```

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.

## Custom Filtering
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
by filtering the list of items yourself and passing the filtered list to the SearchAutocomplete via the `items` prop.
Expand Down
71 changes: 34 additions & 37 deletions packages/@react-spectrum/checkbox/docs/CheckboxGroup.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -156,54 +156,51 @@ function Example() {

## Validation

CheckboxGroups can display a validation state to communicate to the user if the current value is invalid.
Implement your own validation logic in your app and set the `isInvalid` prop to either
the `CheckboxGroup` or an individual `Checkbox` to mark it as invalid.
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.

### Group validation

If the group as a whole is invalid, for example if the user must choose at least one option but failed
to do so, then pass `isInvalid` to the `CheckboxGroup`.

The following example shows how to require that at least one option is selected. It sets the `isInvalid`
prop when no options are selected and removes it otherwise.
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.

```tsx example
function Example() {
let [selected, setSelected] = React.useState([]);

return (
<CheckboxGroup label="Sandwich condiments" value={selected} onChange={setSelected} isRequired isInvalid={selected.length === 0}>
<Checkbox value="lettuce">Lettuce</Checkbox>
<Checkbox value="tomato">Tomato</Checkbox>
<Checkbox value="onion">Onion</Checkbox>
<Checkbox value="sprouts">Sprouts</Checkbox>
</CheckboxGroup>
);
}
import {Form, ButtonGroup, Button} from '@adobe/react-spectrum';

<Form validationBehavior="native">
{/*- begin highlight -*/}
<CheckboxGroup label="Sandwich condiments" name="condiments" isRequired>
{/*- end highlight -*/}
<Checkbox value="lettuce">Lettuce</Checkbox>
<Checkbox value="tomato">Tomato</Checkbox>
<Checkbox value="onion">Onion</Checkbox>
<Checkbox value="sprouts">Sprouts</Checkbox>
</CheckboxGroup>
<ButtonGroup>
<Button type="submit" variant="primary">Submit</Button>
<Button type="reset" variant="secondary">Reset</Button>
</ButtonGroup>
</Form>
```

### Individual Checkbox validation
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.

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

The following example shows how to require that all items are selected. It uses the `isRequired` prop on each individual `Checkbox`
element to indicate to assistive technology that every checkbox is required. By default, the `isRequired` prop on the `CheckboxGroup` only
indicates that the group is required, not any individual option. In addition, `isInvalid` is set on each checkbox that is not yet checked.
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.

```tsx example
function Example() {
let [selected, setSelected] = React.useState([]);

return (
<CheckboxGroup label="Agree to the following" isRequired value={selected} onChange={setSelected}>
<Checkbox value="terms" isRequired isInvalid={!selected.includes('terms')}>Terms and conditions</Checkbox>
<Checkbox value="privacy" isRequired isInvalid={!selected.includes('privacy')}>Privacy policy</Checkbox>
<Checkbox value="cookies" isRequired isInvalid={!selected.includes('cookies')}>Cookie policy</Checkbox>
</CheckboxGroup>
);
}
<Form validationBehavior="native">
<CheckboxGroup label="Agree to the following" isRequired>
{/*- begin highlight -*/}
<Checkbox value="terms" isRequired>Terms and conditions</Checkbox>
<Checkbox value="privacy" isRequired>Privacy policy</Checkbox>
<Checkbox value="cookies" isRequired>Cookie policy</Checkbox>
{/*- end highlight -*/}
</CheckboxGroup>
<ButtonGroup>
<Button type="submit" variant="primary">Submit</Button>
<Button type="reset" variant="secondary">Reset</Button>
</ButtonGroup>
</Form>
```

## Props
Expand Down
56 changes: 19 additions & 37 deletions packages/@react-spectrum/color/docs/ColorField.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,31 +103,26 @@ When the `necessityIndicator` prop is set to `"label"`, a localized string will

## Validation

ColorField can display a validation state to communicate to the user whether the current value is valid or invalid.
Implement your own validation logic in your app and pass either `"valid"` or `"invalid"` to the ColorField via the `validationState` prop.
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.

The example below illustrates how one would validate if the user has entered a red color. In it,
the <TypeLink links={statelyDocs.links} type={statelyDocs.exports.parseColor} /> function is used to parse the
initial color from a hexadecimal string so that `value`'s type remains consistent.
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.

```tsx example
function Example() {
let [value, setValue] = React.useState(parseColor('#e73623'));
let isValid = React.useMemo(() => {
return value && value.getChannelValue('red') > value.getChannelValue('green') && value.getChannelValue('red') > value.getChannelValue('blue');
}, [value]);

return (
<ColorField
validationState={isValid ? 'valid' : 'invalid'}
value={value}
onChange={setValue}
label="Red colors"
/>
);
}
import {Form, ButtonGroup, Button} from '@adobe/react-spectrum';

<Form validationBehavior="native" maxWidth="size-3000">
{/*- begin highlight -*/}
<ColorField label="Color" name="color" isRequired />
{/*- end highlight -*/}
<ButtonGroup>
<Button type="submit" variant="primary">Submit</Button>
<Button type="reset" variant="secondary">Reset</Button>
</ButtonGroup>
</Form>
```

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.

## Props

<PropTable component={docs.exports.ColorField} links={docs.links} />
Expand Down Expand Up @@ -172,23 +167,10 @@ left most edge of the ColorField and "end" refers to the right most edge. For ri
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.

```tsx example
function Example() {
let [value, setValue] = React.useState(parseColor('#e73623'));
let isValid = React.useMemo(() => {
return value && value.getChannelValue('red') > value.getChannelValue('green') && value.getChannelValue('red') > value.getChannelValue('blue');
}, [value]);

return (
<ColorField
validationState={isValid ? 'valid' : 'invalid'}
value={value}
onChange={setValue}
label="Red colors"
description="Enter a red color."
errorMessage={value === null ? 'Empty input not allowed.' : 'Not a red color.'}
/>
);
}
<Flex gap="size-100" wrap>
<ColorField label="Color" defaultValue="#abc" validationState="valid" description="Enter your favorite color." />
<ColorField label="Color" validationState="invalid" errorMessage="Empty input is not allowed." />
</Flex>
```

### Contextual help
Expand Down
54 changes: 23 additions & 31 deletions packages/@react-spectrum/combobox/docs/ComboBox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -659,42 +659,34 @@ function AsyncLoadingExample() {
```

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

The example below illustrates how one would validate if the user has entered a valid email into the ComboBox.
```tsx example
function Example() {
let [value, setValue] = React.useState('me@email.com');
let isValid = React.useMemo(() => /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value), [value]);
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.

let options = [
{id: 1, email: 'fake@email.com'},
{id: 2, email: 'anotherfake@email.com'},
{id: 3, email: 'bob@email.com'},
{id: 4, email: 'joe@email.com'},
{id: 5, email: 'yourEmail@email.com'},
{id: 6, email: 'valid@email.com'},
{id: 7, email: 'spam@email.com'},
{id: 8, email: 'newsletter@email.com'},
{id: 9, email: 'subscribe@email.com'}
];
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.

return (
<ComboBox
width="size-3000"
label="To:"
validationState={isValid ? 'valid' : 'invalid'}
defaultItems={options}
inputValue={value}
onInputChange={setValue}
allowsCustomValue>
{item => <Item>{item.email}</Item>}
</ComboBox>
);
}
```tsx example
import {Form, ButtonGroup, Button} from '@adobe/react-spectrum';

<Form validationBehavior="native" maxWidth="size-3000">
{/*- begin highlight -*/}
<ComboBox label="Favorite animal" name="animal" isRequired>
{/*- end highlight -*/}
<Item>Aardvark</Item>
<Item>Cat</Item>
<Item>Dog</Item>
<Item>Kangaroo</Item>
<Item>Panda</Item>
<Item>Snake</Item>
</ComboBox>
<ButtonGroup>
<Button type="submit" variant="primary">Submit</Button>
<Button type="reset" variant="secondary">Reset</Button>
</ButtonGroup>
</Form>
```

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.

## Custom Filtering
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
by filtering the list of items yourself and passing the filtered list to the ComboBox via the `items` prop.
Expand Down
Loading

0 comments on commit 0ee6a00

Please sign in to comment.