Skip to content

Commit 262cc75

Browse files
nwidynskiLFDanLu
andauthored
fix: checkbox validation reset not working (#7268)
* commit validation on checkbox click * add realtime validation test * remove options from render * fix duplicate import * fix import order * remove unused var * remove unused var * sleep before asserting validation state * remove sleep * enhance checkbox test render * fix grouped scenario in tests * remove useCheckbox from test scenario * remove import * fix: test suite and validation reset on press * fix: excessive state chain * fix: remove state piggyback in favor of onPress * chore: fix import eslint * chore: fix sort imports * chore: lint * Update packages/@react-aria/checkbox/src/useCheckbox.ts Co-authored-by: Daniel Lu <[email protected]> --------- Co-authored-by: Daniel Lu <[email protected]> Co-authored-by: Daniel Lu <[email protected]>
1 parent 3949762 commit 262cc75

File tree

2 files changed

+51
-6
lines changed

2 files changed

+51
-6
lines changed

packages/@react-aria/checkbox/src/useCheckbox.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212

1313
import {AriaCheckboxProps} from '@react-types/checkbox';
1414
import {InputHTMLAttributes, LabelHTMLAttributes, useEffect} from 'react';
15+
import {mergeProps} from '@react-aria/utils';
16+
import {privateValidationStateProp, useFormValidationState} from '@react-stately/form';
1517
import {RefObject, ValidationResult} from '@react-types/shared';
1618
import {ToggleState} from '@react-stately/toggle';
1719
import {useFormValidation} from '@react-aria/form';
18-
import {useFormValidationState} from '@react-stately/form';
20+
import {usePress} from '@react-aria/interactions';
1921
import {useToggle} from '@react-aria/toggle';
2022

2123
export interface CheckboxAria extends ValidationResult {
@@ -61,8 +63,23 @@ export function useCheckbox(props: AriaCheckboxProps, state: ToggleState, inputR
6163
}
6264
});
6365

66+
// Reset validation state on label press for checkbox with a hidden input.
67+
let {pressProps} = usePress({
68+
isDisabled: isDisabled || isReadOnly,
69+
onPress() {
70+
// @ts-expect-error
71+
let {[privateValidationStateProp]: groupValidationState} = props;
72+
73+
let {commitValidation} = groupValidationState
74+
? groupValidationState
75+
: validationState;
76+
77+
commitValidation();
78+
}
79+
});
80+
6481
return {
65-
labelProps,
82+
labelProps: mergeProps(labelProps, pressProps),
6683
inputProps: {
6784
...inputProps,
6885
checked: isSelected,

packages/@react-aria/checkbox/test/useCheckboxGroup.test.tsx

+32-4
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@ import userEvent from '@testing-library/user-event';
2020
function Checkbox({checkboxGroupState, ...props}: AriaCheckboxGroupItemProps & { checkboxGroupState: CheckboxGroupState }) {
2121
const ref = useRef<HTMLInputElement>(null);
2222
const {children} = props;
23-
const {inputProps} = useCheckboxGroupItem(props, checkboxGroupState, ref);
24-
return <label><input ref={ref} {...inputProps} />{children}</label>;
23+
props.validationBehavior ??= 'native';
24+
const {inputProps, labelProps} = useCheckboxGroupItem(props, checkboxGroupState, ref);
25+
return <label {...labelProps}><input ref={ref} {...inputProps} />{children}</label>;
2526
}
2627

2728
function CheckboxGroup({groupProps, checkboxProps}: {groupProps: AriaCheckboxGroupProps, checkboxProps: AriaCheckboxGroupItemProps[]}) {
2829
const state = useCheckboxGroupState(groupProps);
29-
const {groupProps: checkboxGroupProps, labelProps} = useCheckboxGroup(groupProps, state);
30+
const {groupProps: checkboxGroupProps, labelProps, isInvalid} = useCheckboxGroup(groupProps, state);
3031
return (
31-
<div {...checkboxGroupProps}>
32+
<div {...checkboxGroupProps} data-invalid={isInvalid || undefined}>
3233
{groupProps.label && <span {...labelProps}>{groupProps.label}</span>}
3334
<Checkbox checkboxGroupState={state} {...checkboxProps[0]} />
3435
<Checkbox checkboxGroupState={state} {...checkboxProps[1]} />
@@ -281,4 +282,31 @@ describe('useCheckboxGroup', () => {
281282
expect(checkboxOnChangeSpy).toHaveBeenCalledTimes(0);
282283
expect(checkboxes[2].checked).toBeFalsy();
283284
});
285+
286+
it('should re-validate in realtime', async () => {
287+
let {getByRole, getByLabelText} = render(
288+
<CheckboxGroup
289+
groupProps={{label: 'Favorite Pet', isRequired: true}}
290+
checkboxProps={[
291+
{value: 'dogs', children: 'Dogs'},
292+
{value: 'cats', children: 'Cats'},
293+
{value: 'dragons', children: 'Dragons'}
294+
]} />
295+
);
296+
297+
let checkboxGroup = getByRole('group');
298+
let dragons = getByLabelText('Dragons');
299+
300+
await user.click(dragons);
301+
302+
expect(checkboxGroup).not.toHaveAttribute('data-invalid');
303+
304+
await user.click(dragons);
305+
306+
expect(checkboxGroup).toHaveAttribute('data-invalid', 'true');
307+
308+
await user.click(dragons);
309+
310+
expect(checkboxGroup).not.toHaveAttribute('data-invalid');
311+
});
284312
});

0 commit comments

Comments
 (0)