From 25e29c51666ff1fb439da5b565e78a9f5caa3673 Mon Sep 17 00:00:00 2001 From: levinqdl Date: Fri, 27 Sep 2024 06:21:57 +0800 Subject: [PATCH] Fix checkbox defaultChecked unexpected changing in form (#2135) --- .yarn/versions/a0df87d9.yml | 5 ++ packages/react/checkbox/src/Checkbox.test.tsx | 62 +++++++++++++++++++ packages/react/checkbox/src/Checkbox.tsx | 8 ++- 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 .yarn/versions/a0df87d9.yml diff --git a/.yarn/versions/a0df87d9.yml b/.yarn/versions/a0df87d9.yml new file mode 100644 index 000000000..da7e3b7c7 --- /dev/null +++ b/.yarn/versions/a0df87d9.yml @@ -0,0 +1,5 @@ +releases: + "@radix-ui/react-checkbox": patch + +declined: + - primitives diff --git a/packages/react/checkbox/src/Checkbox.test.tsx b/packages/react/checkbox/src/Checkbox.test.tsx index 7172d668d..c10c37541 100644 --- a/packages/react/checkbox/src/Checkbox.test.tsx +++ b/packages/react/checkbox/src/Checkbox.test.tsx @@ -120,6 +120,68 @@ describe('given a controlled `checked` Checkbox', () => { }); }); +describe('given an uncontrolled Checkbox in form', () => { + describe('when clicking the checkbox', () => { + it('should receive change event with target `defaultChecked` same as the `defaultChecked` prop of Checkbox', (done) => { + const rendered = render( +
{ + const target = event.target as HTMLInputElement; + expect(target.defaultChecked).toBe(true); + }} + > + + + ); + const checkbox = rendered.getByRole(CHECKBOX_ROLE); + fireEvent.click(checkbox); + rendered.rerender( +
{ + const target = event.target as HTMLInputElement; + expect(target.defaultChecked).toBe(false); + done(); + }} + > + + + ); + fireEvent.click(checkbox); + }); + }); +}); + +describe('given a controlled Checkbox in a form', () => { + describe('when clicking the checkbox', () => { + it('should receive change event with target `defaultChecked` same as initial value of `checked` of Checkbox', (done) => { + const rendered = render( +
{ + const target = event.target as HTMLInputElement; + expect(target.defaultChecked).toBe(true); + }} + > + + + ); + const checkbox = rendered.getByRole(CHECKBOX_ROLE); + fireEvent.click(checkbox); + rendered.rerender( +
{ + const target = event.target as HTMLInputElement; + expect(target.defaultChecked).toBe(true); + done(); + }} + > + + + ); + fireEvent.click(checkbox); + }); + }); +}); + function CheckboxTest(props: React.ComponentProps) { const containerRef = React.useRef(null); React.useEffect(() => { diff --git a/packages/react/checkbox/src/Checkbox.tsx b/packages/react/checkbox/src/Checkbox.tsx index a01325edd..5b0bc80d5 100644 --- a/packages/react/checkbox/src/Checkbox.tsx +++ b/packages/react/checkbox/src/Checkbox.tsx @@ -19,7 +19,7 @@ const CHECKBOX_NAME = 'Checkbox'; type ScopedProps

= P & { __scopeCheckbox?: Scope }; const [createCheckboxContext, createCheckboxScope] = createContextScope(CHECKBOX_NAME); -type CheckedState = boolean | 'indeterminate'; +export type CheckedState = boolean | 'indeterminate'; type CheckboxContextValue = { state: CheckedState; @@ -112,6 +112,7 @@ const Checkbox = React.forwardRef( // rendered it **after** the button. This pulls it back to sit on top // of the button. style={{ transform: 'translateX(-100%)' }} + defaultChecked={isIndeterminate(defaultChecked) ? false : defaultChecked} /> )} @@ -167,7 +168,7 @@ interface BubbleInputProps extends Omit { } const BubbleInput = (props: BubbleInputProps) => { - const { control, checked, bubbles = true, ...inputProps } = props; + const { control, checked, bubbles = true, defaultChecked, ...inputProps } = props; const ref = React.useRef(null); const prevChecked = usePrevious(checked); const controlSize = useSize(control); @@ -187,11 +188,12 @@ const BubbleInput = (props: BubbleInputProps) => { } }, [prevChecked, checked, bubbles]); + const defaultCheckedRef = React.useRef(isIndeterminate(checked) ? false : checked); return (