Skip to content

Commit 4d89a43

Browse files
authored
refactor hook and support ref (#47)
* refactor hook and support ref * change lint * fix test lint * add prettierrc and fix test lint * add more test * fix lint * remove not use code * add more test * Update index.spec.js * Create .eslintignore
1 parent 34b73c8 commit 4d89a43

File tree

6 files changed

+142
-133
lines changed

6 files changed

+142
-133
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.eslintrc.js

.prettierrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"endOfLine": "lf",
3+
"semi": true,
4+
"singleQuote": true,
5+
"tabWidth": 2,
6+
"trailingComma": "all",
7+
"printWidth": 100
8+
}

examples/simple.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export default class Simple extends React.Component {
2525
<div style={{ margin: 20 }}>
2626
<Switch
2727
onChange={onChange}
28-
onClick={onChange}
2928
disabled={disabled}
3029
checkedChildren="开"
3130
unCheckedChildren="关"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"compile": "father build && lessc assets/index.less assets/index.css",
3131
"gh-pages": "father doc deploy",
3232
"prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish",
33-
"lint": "eslint src/ --ext .jsx,.js,.md",
33+
"lint": "eslint .",
3434
"test": "father test",
3535
"coverage": "father test --coverage"
3636
},

src/index.tsx

Lines changed: 79 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { Component, MouseEventHandler } from 'react';
1+
import * as React from 'react';
22
import classNames from 'classnames';
33

44
export type SwitchChangeEventHandler = (checked: boolean, event: MouseEvent) => void;
@@ -11,7 +11,7 @@ interface SwitchProps {
1111
checkedChildren?: React.ReactNode;
1212
unCheckedChildren?: React.ReactNode;
1313
onChange?: SwitchChangeEventHandler;
14-
onMouseUp: MouseEventHandler<HTMLButtonElement>;
14+
onMouseUp: React.MouseEventHandler<HTMLButtonElement>;
1515
onClick?: SwitchClickEventHandler;
1616
tabIndex?: number;
1717
checked?: boolean;
@@ -22,143 +22,115 @@ interface SwitchProps {
2222
title?: string;
2323
}
2424

25-
interface SwitchState {
26-
checked: boolean;
27-
}
28-
29-
class Switch extends Component<SwitchProps, SwitchState> {
30-
private node: React.RefObject<HTMLButtonElement>;
25+
const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>((props, ref) => {
26+
const mergedRef = (ref as any) || React.createRef<HTMLButtonElement>();
3127

32-
static defaultProps = {
33-
prefixCls: 'rc-switch',
34-
checkedChildren: null,
35-
unCheckedChildren: null,
36-
className: '',
37-
defaultChecked: false,
38-
};
39-
40-
constructor(props) {
41-
super(props);
42-
let checked = false;
43-
if ('checked' in props) {
44-
checked = !!props.checked;
45-
} else {
46-
checked = !!props.defaultChecked;
47-
}
48-
this.state = { checked };
49-
this.node = React.createRef();
28+
let initChecked = false;
29+
if ('checked' in props) {
30+
initChecked = !!props.checked;
31+
} else {
32+
initChecked = !!props.defaultChecked;
5033
}
34+
const [checked, setChecked] = React.useState(initChecked);
5135

52-
componentDidMount() {
53-
const { autoFocus, disabled } = this.props;
36+
React.useEffect(() => {
37+
const { autoFocus, disabled } = props;
5438
if (autoFocus && !disabled) {
55-
this.focus();
39+
focus();
5640
}
57-
}
41+
}, [props.autoFocus, props.disabled]);
5842

59-
static getDerivedStateFromProps(nextProps) {
60-
const { checked } = nextProps;
61-
const newState: Partial<SwitchState> = {};
62-
if ('checked' in nextProps) {
63-
newState.checked = !!checked;
43+
React.useEffect(() => {
44+
if ('checked' in props) {
45+
setChecked(!!props.checked);
6446
}
65-
return newState;
66-
}
47+
}, [props.checked]);
6748

68-
setChecked(checked, e) {
69-
const { disabled, onChange } = this.props;
49+
const setInternalChecked = (checked, e) => {
50+
const { disabled, onChange } = props;
7051
if (disabled) {
7152
return;
7253
}
73-
if (!('checked' in this.props)) {
74-
this.setState({
75-
checked,
76-
});
54+
if (!('checked' in props)) {
55+
setChecked(checked);
7756
}
7857
if (onChange) {
7958
onChange(checked, e);
8059
}
81-
}
60+
};
8261

83-
handleClick = e => {
84-
const { checked } = this.state;
85-
const { onClick } = this.props;
62+
const handleClick = e => {
63+
const { onClick } = props;
8664
const newChecked = !checked;
87-
this.setChecked(newChecked, e);
65+
setInternalChecked(newChecked, e);
8866
if (onClick) {
8967
onClick(newChecked, e);
9068
}
9169
};
9270

93-
handleKeyDown = e => {
71+
const handleKeyDown = e => {
9472
if (e.keyCode === 37) {
9573
// Left
96-
this.setChecked(false, e);
74+
setInternalChecked(false, e);
9775
} else if (e.keyCode === 39) {
9876
// Right
99-
this.setChecked(true, e);
77+
setInternalChecked(true, e);
10078
}
10179
};
10280

10381
// Handle auto focus when click switch in Chrome
104-
handleMouseUp = e => {
105-
const { onMouseUp } = this.props;
106-
this.blur();
107-
if (onMouseUp) {
108-
onMouseUp(e);
82+
const handleMouseUp = e => {
83+
(mergedRef.current as any).blur();
84+
if (props.onMouseUp) {
85+
props.onMouseUp(e);
10986
}
11087
};
11188

112-
focus() {
113-
if (this.node.current) {
114-
this.node.current.focus();
115-
}
116-
}
117-
118-
blur() {
119-
if (this.node.current) {
120-
this.node.current.blur();
121-
}
122-
}
123-
124-
render() {
125-
const {
126-
className,
127-
prefixCls,
128-
disabled,
129-
loadingIcon,
130-
checkedChildren,
131-
unCheckedChildren,
132-
onChange,
133-
...restProps
134-
} = this.props;
135-
const { checked } = this.state;
136-
const switchClassName = classNames({
137-
[className]: !!className,
138-
[prefixCls]: true,
139-
[`${prefixCls}-checked`]: checked,
140-
[`${prefixCls}-disabled`]: disabled,
141-
});
142-
return (
143-
<button
144-
{...restProps}
145-
type="button"
146-
role="switch"
147-
aria-checked={checked}
148-
disabled={disabled}
149-
className={switchClassName}
150-
ref={this.node}
151-
onKeyDown={this.handleKeyDown}
152-
onClick={this.handleClick}
153-
onMouseUp={this.handleMouseUp}
154-
>
155-
{loadingIcon}
156-
<span className={`${prefixCls}-inner`}>
157-
{checked ? checkedChildren : unCheckedChildren}
158-
</span>
159-
</button>
160-
);
161-
}
162-
}
89+
const {
90+
className,
91+
prefixCls,
92+
disabled,
93+
loadingIcon,
94+
checkedChildren,
95+
unCheckedChildren,
96+
onChange,
97+
...restProps
98+
} = props;
99+
100+
const switchClassName = classNames({
101+
[className]: !!className,
102+
[prefixCls]: true,
103+
[`${prefixCls}-checked`]: checked,
104+
[`${prefixCls}-disabled`]: disabled,
105+
});
106+
107+
return (
108+
<button
109+
{...restProps}
110+
type="button"
111+
role="switch"
112+
aria-checked={checked}
113+
disabled={disabled}
114+
className={switchClassName}
115+
ref={mergedRef}
116+
onKeyDown={handleKeyDown}
117+
onClick={handleClick}
118+
onMouseUp={handleMouseUp}
119+
>
120+
{loadingIcon}
121+
<span className={`${prefixCls}-inner`}>{checked ? checkedChildren : unCheckedChildren}</span>
122+
</button>
123+
);
124+
});
125+
126+
Switch.displayName = 'Switch';
127+
128+
Switch.defaultProps = {
129+
prefixCls: 'rc-switch',
130+
checkedChildren: null,
131+
unCheckedChildren: null,
132+
className: '',
133+
defaultChecked: false,
134+
};
163135

164136
export default Switch;

tests/index.spec.js

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,44 @@ import { mount } from 'enzyme';
44
import Switch from '../index';
55

66
describe('rc-switch', () => {
7-
let switcher;
8-
beforeEach(() => {
9-
switcher = mount(<Switch />);
10-
});
7+
function createSwitch(props = {}) {
8+
return mount(
9+
<Switch
10+
checkedChildren={<span className="checked" />}
11+
unCheckedChildren={<span className="unchecked" />}
12+
{...props}
13+
/>,
14+
);
15+
}
1116

1217
it('works', () => {
13-
expect(switcher.state().checked).toBe(false);
14-
switcher.simulate('click');
15-
expect(switcher.state().checked).toBe(true);
18+
const wrapper = createSwitch();
19+
expect(wrapper.exists('.unchecked')).toBeTruthy();
20+
wrapper.simulate('click');
21+
expect(wrapper.exists('.checked')).toBeTruthy();
1622
});
1723

1824
it('should be checked upon right key and unchecked on left key', () => {
19-
expect(switcher.state().checked).toBe(false);
20-
switcher.simulate('keydown', { keyCode: 39 });
21-
expect(switcher.state().checked).toBe(true);
22-
switcher.simulate('keydown', { keyCode: 37 });
23-
expect(switcher.state().checked).toBe(false);
25+
const wrapper = createSwitch();
26+
expect(wrapper.exists('.unchecked')).toBeTruthy();
27+
wrapper.simulate('keydown', { keyCode: 39 });
28+
expect(wrapper.exists('.checked')).toBeTruthy();
29+
wrapper.simulate('keydown', { keyCode: 37 });
30+
expect(wrapper.exists('.unchecked')).toBeTruthy();
2431
});
2532

2633
it('should change from an initial checked state of true to false on click', () => {
27-
const wrapper = mount(<Switch defaultChecked />);
28-
expect(wrapper.state().checked).toBe(true);
34+
const onChange = jest.fn();
35+
const wrapper = createSwitch({ defaultChecked: true, onChange });
36+
expect(wrapper.exists('.checked')).toBeTruthy();
2937
wrapper.simulate('click');
30-
expect(wrapper.state().checked).toBe(false);
38+
expect(wrapper.exists('.unchecked')).toBeTruthy();
39+
expect(onChange.mock.calls.length).toBe(1);
3140
});
3241

3342
it('should support onClick', () => {
3443
const onClick = jest.fn();
35-
const wrapper = mount(<Switch onClick={onClick} />);
44+
const wrapper = createSwitch({ onClick });
3645
wrapper.simulate('click');
3746
expect(onClick).toHaveBeenCalledWith(true, expect.objectContaining({ type: 'click' }));
3847
expect(onClick.mock.calls.length).toBe(1);
@@ -43,10 +52,10 @@ describe('rc-switch', () => {
4352

4453
it('should not toggle when clicked in a disabled state', () => {
4554
const onChange = jest.fn();
46-
const wrapper = mount(<Switch disabled checked onChange={onChange} />);
47-
expect(wrapper.state().checked).toBe(true);
55+
const wrapper = createSwitch({ disabled: true, checked: true, onChange });
56+
expect(wrapper.exists('.checked')).toBeTruthy();
4857
wrapper.simulate('click');
49-
expect(wrapper.state().checked).toBe(true);
58+
expect(wrapper.exists('.checked')).toBeTruthy();
5059
expect(onChange.mock.calls.length).toBe(0);
5160
});
5261

@@ -59,18 +68,24 @@ describe('rc-switch', () => {
5968
const container = document.createElement('div');
6069
document.body.appendChild(container);
6170
const handleFocus = jest.fn();
62-
const wrapper = mount(<Switch onFocus={handleFocus} />, { attachTo: container });
63-
wrapper.instance().focus();
71+
const ref = React.createRef();
72+
mount(<Switch ref={ref} onFocus={handleFocus} />, {
73+
attachTo: container,
74+
});
75+
ref.current.focus();
6476
expect(handleFocus).toHaveBeenCalled();
6577
});
6678

6779
it('blur()', () => {
6880
const container = document.createElement('div');
6981
document.body.appendChild(container);
7082
const handleBlur = jest.fn();
71-
const wrapper = mount(<Switch onBlur={handleBlur} />, { attachTo: container });
72-
wrapper.instance().focus();
73-
wrapper.instance().blur();
83+
const ref = React.createRef();
84+
mount(<Switch ref={ref} onBlur={handleBlur} />, {
85+
attachTo: container,
86+
});
87+
ref.current.focus();
88+
ref.current.blur();
7489
expect(handleBlur).toHaveBeenCalled();
7590
});
7691

@@ -81,4 +96,18 @@ describe('rc-switch', () => {
8196
mount(<Switch autoFocus onFocus={handleFocus} />, { attachTo: container });
8297
expect(handleFocus).toHaveBeenCalled();
8398
});
99+
100+
it('disabled', () => {
101+
const wrapper = createSwitch({ disabled: true });
102+
expect(wrapper.exists('.unchecked')).toBeTruthy();
103+
wrapper.simulate('keydown', { keyCode: 39 });
104+
expect(wrapper.exists('.unchecked')).toBeTruthy();
105+
});
106+
107+
it('onMouseUp', () => {
108+
const onMouseUp = jest.fn();
109+
const wrapper = createSwitch({ onMouseUp });
110+
wrapper.simulate('mouseup');
111+
expect(onMouseUp).toHaveBeenCalled();
112+
});
84113
});

0 commit comments

Comments
 (0)