Skip to content

Commit c643e8c

Browse files
committed
Fix focus in controlled and uncontrolled mode
1 parent 467cf89 commit c643e8c

File tree

2 files changed

+51
-39
lines changed

2 files changed

+51
-39
lines changed

src/components/Tabs.js

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,56 +26,69 @@ export default class Tabs extends Component {
2626
constructor(props) {
2727
super(props);
2828

29-
if (this.props.selectedIndex === null) {
30-
this.state = Tabs.copyPropsToState(this.props, {});
31-
} else {
32-
this.state = {};
33-
}
29+
this.state = Tabs.copyPropsToState(this.props, {});
3430
}
3531

3632
componentWillReceiveProps(newProps) {
37-
if (this.props.selectedIndex === null) {
38-
// Use a transactional update to prevent race conditions
39-
// when reading the state in copyPropsToState
40-
// See https://github.com/reactjs/react-tabs/issues/51
41-
this.setState(state => Tabs.copyPropsToState(newProps, state));
33+
if (
34+
process.env.NODE_ENV !== 'production' &&
35+
Tabs.inUncontrolledMode(newProps) !== Tabs.inUncontrolledMode(this.props)
36+
) {
37+
throw new Error(
38+
`Switching between controlled mode (by using \`selectedIndex\`) and uncontrolled mode is not supported in \`Tabs\`.
39+
For more information about controlled and uncontrolled mode of react-tabs see the README.`,
40+
);
4241
}
42+
// Use a transactional update to prevent race conditions
43+
// when reading the state in copyPropsToState
44+
// See https://github.com/reactjs/react-tabs/issues/51
45+
this.setState(state => Tabs.copyPropsToState(newProps, state));
46+
}
47+
48+
static inUncontrolledMode(props) {
49+
return props.selectedIndex === null;
4350
}
4451

4552
handleSelected = (index, last, event) => {
46-
if (this.state.selectedIndex != null) {
47-
// Check if the change event handler cancels the tab change
48-
let cancel = false;
53+
const state = {
54+
// Set focus if the change was triggered from the keyboard
55+
focus: event.type === 'keydown',
56+
};
4957

50-
// Call change event handler
51-
if (typeof this.props.onSelect === 'function') {
52-
cancel = this.props.onSelect(index, last, event) === false;
53-
}
58+
// Call change event handler
59+
if (typeof this.props.onSelect === 'function') {
60+
// Check if the change event handler cancels the tab change
61+
if (this.props.onSelect(index, last, event) === false) return;
62+
}
5463

55-
if (!cancel) {
56-
// Update selected index
57-
// Set focus if the change was triggered from the keyboard
58-
this.setState({ selectedIndex: index, focus: event instanceof KeyboardEvent });
59-
}
64+
if (Tabs.inUncontrolledMode(this.props)) {
65+
// Update selected index
66+
state.selectedIndex = index;
6067
}
68+
69+
this.setState(state);
6170
}
6271

6372
// preserve the existing selectedIndex from state.
6473
// If the state has not selectedIndex, default to the defaultIndex or 0
6574
static copyPropsToState(props, state) {
66-
const maxTabIndex = getTabsCount(props.children) - 1;
67-
let selectedIndex = null;
75+
const newState = {
76+
focus: state.focus || props.defaultFocus,
77+
};
78+
79+
if (Tabs.inUncontrolledMode(props)) {
80+
const maxTabIndex = getTabsCount(props.children) - 1;
81+
let selectedIndex = null;
6882

69-
if (state.selectedIndex != null) {
70-
selectedIndex = Math.min(state.selectedIndex, maxTabIndex);
71-
} else {
72-
selectedIndex = props.defaultIndex || 0;
83+
if (state.selectedIndex != null) {
84+
selectedIndex = Math.min(state.selectedIndex, maxTabIndex);
85+
} else {
86+
selectedIndex = props.defaultIndex || 0;
87+
}
88+
newState.selectedIndex = selectedIndex;
7389
}
7490

75-
return {
76-
selectedIndex,
77-
focus: state.focus || props.defaultFocus,
78-
};
91+
return newState;
7992
}
8093

8194
render() {
@@ -100,9 +113,10 @@ export default class Tabs extends Component {
100113

101114
const { children, defaultIndex, defaultFocus, ...props } = this.props;
102115

116+
props.focus = this.state.focus;
117+
props.onSelect = this.handleSelected;
118+
103119
if (this.state.selectedIndex != null) {
104-
props.focus = this.state.focus;
105-
props.onSelect = this.handleSelected;
106120
props.selectedIndex = this.state.selectedIndex;
107121
}
108122

src/components/UncontrolledTabs.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,14 +186,12 @@ export default class UncontrolledTabs extends Component {
186186
let index = this.props.selectedIndex;
187187
let preventDefault = false;
188188

189-
// Select next tab to the left
190189
if (e.keyCode === 37 || e.keyCode === 38) {
190+
// Select next tab to the left
191191
index = this.getPrevTab(index);
192192
preventDefault = true;
193-
}
194-
// Select next tab to the right
195-
/* eslint brace-style:0 */
196-
else if (e.keyCode === 39 || e.keyCode === 40) {
193+
} else if (e.keyCode === 39 || e.keyCode === 40) {
194+
// Select next tab to the right
197195
index = this.getNextTab(index);
198196
preventDefault = true;
199197
}

0 commit comments

Comments
 (0)