feat(switch): support for value and defaultValue props (#45747)

* feat(switch): support for `value` and `defaultValue` props

* docs: update docs

* chore: add unit case

(cherry picked from commit d25977cd16e27949ccfe9f1533b3d16bb6c3abba)

* chore: update case

* chore: remove skip test case
This commit is contained in:
2023-11-09 13:42:25 +08:00 committed by GitHub
parent 53b4235796
commit 0da10b6cca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 126 additions and 46 deletions

View File

@ -2102,4 +2102,44 @@ describe('Form', () => {
expect(container.querySelector('.ant-input-number-suffix')).toBeTruthy();
expect(container.querySelector('.ant-input-number-focused')).toBeTruthy();
});
// https://github.com/ant-design/ant-design/issues/20803#issuecomment-601626759
it('without explicitly passing `valuePropName`', async () => {
const submit = jest.fn();
const Demo = () => (
<Form
initialValues={{
foo: true,
}}
onFinish={submit}
>
<Form.Item label="Switch" name="foo">
<Switch />
</Form.Item>
<button type="submit">Submit</button>
</Form>
);
const { getByRole } = render(<Demo />);
await waitFakeTimer();
const switchNode = getByRole('switch');
expect(switchNode).toBeTruthy();
expect(switchNode).toBeChecked();
fireEvent.click(switchNode);
expect(switchNode).not.toBeChecked();
const submitButton = getByRole('button');
expect(submitButton).toBeTruthy();
fireEvent.click(submitButton);
await waitFakeTimer();
expect(submit).toHaveBeenCalledWith({
foo: false,
});
});
});

View File

@ -4,7 +4,6 @@ import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { act, fireEvent, render } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';
jest.mock('rc-util/lib/Dom/isVisible', () => {
const mockFn = () => true;
@ -33,15 +32,35 @@ describe('Switch', () => {
jest.useRealTimers();
});
it('warning if set `value`', () => {
resetWarned();
it('should be controlled by value', () => {
const mockChangeHandler = jest.fn();
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const props = { value: true } as any;
render(<Switch {...props} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Switch] `value` is not a valid prop, do you mean `checked`?',
);
errorSpy.mockRestore();
const { getByRole } = render(<Switch value onChange={mockChangeHandler} />);
const switchNode = getByRole('switch');
expect(switchNode).toBeTruthy();
expect(getByRole('switch')).toBeChecked();
fireEvent.click(switchNode);
expect(mockChangeHandler).toHaveBeenCalledWith(false, expect.anything());
// controlled component, so still true after click
expect(getByRole('switch')).toBeChecked();
});
it('should be uncontrolled by defaultValue', () => {
const mockChangeHandler = jest.fn();
const { getByRole } = render(<Switch defaultValue onChange={mockChangeHandler} />);
const switchNode = getByRole('switch');
expect(switchNode).toBeTruthy();
expect(getByRole('switch')).toBeChecked();
fireEvent.click(switchNode);
expect(mockChangeHandler).toHaveBeenCalledWith(false, expect.anything());
// uncontrolled component, so false after click
expect(getByRole('switch')).not.toBeChecked();
});
});

View File

@ -29,19 +29,21 @@ Switching Selector.
Common props ref[Common props](/docs/react/common-props)
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| autoFocus | Whether get focus when component mounted | boolean | false |
| checked | Determine whether the Switch is checked | boolean | false |
| checkedChildren | The content to be shown when the state is checked | ReactNode | - |
| className | The additional class to Switch | string | - |
| defaultChecked | Whether to set the initial state | boolean | false |
| disabled | Disable switch | boolean | false |
| loading | Loading state of switch | boolean | false |
| size | The size of the Switch, options: `default` `small` | string | `default` |
| unCheckedChildren | The content to be shown when the state is unchecked | ReactNode | - |
| onChange | Trigger when the checked state is changing | function(checked: boolean, event: Event) | - |
| onClick | Trigger when clicked | function(checked: boolean, event: Event) | - |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| autoFocus | Whether get focus when component mounted | boolean | false | |
| checked | Determine whether the Switch is checked | boolean | false | |
| checkedChildren | The content to be shown when the state is checked | ReactNode | - | |
| className | The additional class to Switch | string | - | |
| defaultChecked | Whether to set the initial state | boolean | false | |
| defaultValue | Alias for `defaultChecked` | boolean | - | 5.12.0 |
| disabled | Disable switch | boolean | false | |
| loading | Loading state of switch | boolean | false | |
| size | The size of the Switch, options: `default` `small` | string | `default` | |
| unCheckedChildren | The content to be shown when the state is unchecked | ReactNode | - | |
| value | Alias for `checked` | boolean | - | 5.12.0 |
| onChange | Trigger when the checked state is changing | function(checked: boolean, event: Event) | - | |
| onClick | Trigger when clicked | function(checked: boolean, event: Event) | - | |
## Methods

View File

@ -3,12 +3,12 @@ import LoadingOutlined from '@ant-design/icons/LoadingOutlined';
import classNames from 'classnames';
import RcSwitch from 'rc-switch';
import { devUseWarning } from '../_util/warning';
import Wave from '../_util/wave';
import { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import useSize from '../config-provider/hooks/useSize';
import useStyle from './style';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
export type SwitchSize = 'small' | 'default';
export type SwitchChangeEventHandler = (
@ -24,6 +24,16 @@ export interface SwitchProps {
rootClassName?: string;
checked?: boolean;
defaultChecked?: boolean;
/**
* Alias for `checked`.
* @since 5.12.0
*/
value?: boolean;
/**
* Alias for `defaultChecked`.
* @since 5.12.0
*/
defaultValue?: boolean;
onChange?: SwitchChangeEventHandler;
onClick?: SwitchClickEventHandler;
checkedChildren?: React.ReactNode;
@ -53,18 +63,18 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>((props, ref) =>
className,
rootClassName,
style,
checked: checkedProp,
value,
defaultChecked: defaultCheckedProp,
defaultValue,
onChange,
...restProps
} = props;
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Switch');
warning(
'checked' in props || !('value' in props),
'usage',
'`value` is not a valid prop, do you mean `checked`?',
);
}
const [checked, setChecked] = useMergedState<boolean>(false, {
value: checkedProp ?? value,
defaultValue: defaultCheckedProp ?? defaultValue,
});
const { getPrefixCls, direction, switch: SWITCH } = React.useContext(ConfigContext);
@ -99,10 +109,17 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>((props, ref) =>
const mergedStyle: React.CSSProperties = { ...SWITCH?.style, ...style };
const changeHandler: SwitchChangeEventHandler = (...args) => {
setChecked(args[0]);
onChange?.(...args);
};
return wrapSSR(
<Wave component="Switch">
<RcSwitch
{...restProps}
checked={checked}
onChange={changeHandler}
prefixCls={prefixCls}
className={classes}
style={mergedStyle}

View File

@ -30,19 +30,21 @@ demo:
通用属性参考:[通用属性](/docs/react/common-props)
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| autoFocus | 组件自动获取焦点 | boolean | false |
| checked | 指定当前是否选中 | boolean | false |
| checkedChildren | 选中时的内容 | ReactNode | - |
| className | Switch 器类名 | string | - |
| defaultChecked | 初始是否选中 | boolean | false |
| disabled | 是否禁用 | boolean | false |
| loading | 加载中的开关 | boolean | false |
| size | 开关大小,可选值:`default` `small` | string | `default` |
| unCheckedChildren | 非选中时的内容 | ReactNode | - |
| onChange | 变化时的回调函数 | function(checked: boolean, event: Event) | - |
| onClick | 点击时的回调函数 | function(checked: boolean, event: Event) | - |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autoFocus | 组件自动获取焦点 | boolean | false | |
| checked | 指定当前是否选中 | boolean | false | |
| checkedChildren | 选中时的内容 | ReactNode | - | |
| className | Switch 器类名 | string | - | |
| defaultChecked | 初始是否选中 | boolean | false | |
| defaultValue | `defaultChecked` 的别名 | boolean | - | 5.12.0 |
| disabled | 是否禁用 | boolean | false | |
| loading | 加载中的开关 | boolean | false | |
| size | 开关大小,可选值:`default` `small` | string | `default` | |
| unCheckedChildren | 非选中时的内容 | ReactNode | - | |
| value | `checked` 的别名 | boolean | - | 5.12.0 |
| onChange | 变化时的回调函数 | function(checked: boolean, event: Event) | - | |
| onClick | 点击时的回调函数 | function(checked: boolean, event: Event) | - | |
## 方法