diff --git a/src/components/Tab.js b/src/components/Tab.js
index 256070a504..8c0c5dc670 100644
--- a/src/components/Tab.js
+++ b/src/components/Tab.js
@@ -1,96 +1,91 @@
import PropTypes from 'prop-types';
-import React, { Component } from 'react';
+import React, { useEffect, useRef } from 'react';
import cx from 'clsx';
const DEFAULT_CLASS = 'react-tabs__tab';
+const DEFAULT_PROPS = {
+ className: DEFAULT_CLASS,
+ disabledClassName: `${DEFAULT_CLASS}--disabled`,
+ focus: false,
+ id: null,
+ panelId: null,
+ selected: false,
+ selectedClassName: `${DEFAULT_CLASS}--selected`,
+};
-export default class Tab extends Component {
- static defaultProps = {
- className: DEFAULT_CLASS,
- disabledClassName: `${DEFAULT_CLASS}--disabled`,
- focus: false,
- id: null,
- panelId: null,
- selected: false,
- selectedClassName: `${DEFAULT_CLASS}--selected`,
- };
-
- static propTypes = {
- children: PropTypes.oneOfType([
- PropTypes.array,
- PropTypes.object,
- PropTypes.string,
- ]),
- className: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.array,
- PropTypes.object,
- ]),
- disabled: PropTypes.bool,
- tabIndex: PropTypes.string,
- disabledClassName: PropTypes.string,
- focus: PropTypes.bool, // private
- id: PropTypes.string, // private
- panelId: PropTypes.string, // private
- selected: PropTypes.bool, // private
- selectedClassName: PropTypes.string,
- tabRef: PropTypes.func, // private
- };
-
- componentDidMount() {
- this.checkFocus();
- }
+const propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.array,
+ PropTypes.object,
+ PropTypes.string,
+ ]),
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object,
+ ]),
+ disabled: PropTypes.bool,
+ tabIndex: PropTypes.string,
+ disabledClassName: PropTypes.string,
+ focus: PropTypes.bool, // private
+ id: PropTypes.string, // private
+ panelId: PropTypes.string, // private
+ selected: PropTypes.bool, // private
+ selectedClassName: PropTypes.string,
+ tabRef: PropTypes.func,
+};
- componentDidUpdate() {
- this.checkFocus();
- }
-
- checkFocus() {
- const { selected, focus } = this.props;
+const Tab = (props) => {
+ let nodeRef = useRef();
+ const checkFocus = () => {
+ const { selected, focus } = props;
if (selected && focus) {
- this.node.focus();
+ nodeRef.current.focus();
}
- }
-
- render() {
- const {
- children,
- className,
- disabled,
- disabledClassName,
- focus, // unused
- id,
- panelId,
- selected,
- selectedClassName,
- tabIndex,
- tabRef,
- ...attributes
- } = this.props;
+ };
+ useEffect(() => {
+ checkFocus();
+ });
+ const {
+ children,
+ className,
+ disabled,
+ disabledClassName,
+ focus, // unused
+ id,
+ panelId,
+ selected,
+ selectedClassName,
+ tabIndex,
+ tabRef,
+ ...attributes
+ } = props;
- return (
-
{
- this.node = node;
- if (tabRef) tabRef(node);
- }}
- role="tab"
- id={id}
- aria-selected={selected ? 'true' : 'false'}
- aria-disabled={disabled ? 'true' : 'false'}
- aria-controls={panelId}
- tabIndex={tabIndex || (selected ? '0' : null)}
- data-rttab
- >
- {children}
-
- );
- }
-}
+ return (
+ {
+ nodeRef.current = node;
+ if (tabRef) tabRef(node);
+ }}
+ role="tab"
+ id={id}
+ aria-selected={selected ? 'true' : 'false'}
+ aria-disabled={disabled ? 'true' : 'false'}
+ aria-controls={panelId}
+ tabIndex={tabIndex || (selected ? '0' : null)}
+ data-rttab
+ >
+ {children}
+
+ );
+};
+Tab.propTypes = propTypes;
Tab.tabsRole = 'Tab';
+Tab.defaultProps = DEFAULT_PROPS;
+export default Tab;
diff --git a/src/components/TabList.js b/src/components/TabList.js
index bac4f3fe3a..425bc83178 100644
--- a/src/components/TabList.js
+++ b/src/components/TabList.js
@@ -1,30 +1,29 @@
import PropTypes from 'prop-types';
-import React, { Component } from 'react';
+import React from 'react';
import cx from 'clsx';
-export default class TabList extends Component {
- static defaultProps = {
- className: 'react-tabs__tab-list',
- };
+const defaultProps = {
+ className: 'react-tabs__tab-list',
+};
+const propTypes = {
+ children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object,
+ ]),
+};
+const TabList = (props) => {
+ const { children, className, ...attributes } = props;
- static propTypes = {
- children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
- className: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.array,
- PropTypes.object,
- ]),
- };
-
- render() {
- const { children, className, ...attributes } = this.props;
-
- return (
-
- );
- }
-}
+ return (
+
+ );
+};
TabList.tabsRole = 'TabList';
+TabList.propTypes = propTypes;
+TabList.defaultProps = defaultProps;
+export default TabList;
diff --git a/src/components/TabPanel.js b/src/components/TabPanel.js
index 894de1f855..b0c325d2c5 100644
--- a/src/components/TabPanel.js
+++ b/src/components/TabPanel.js
@@ -1,56 +1,54 @@
import PropTypes from 'prop-types';
-import React, { Component } from 'react';
+import React from 'react';
import cx from 'clsx';
const DEFAULT_CLASS = 'react-tabs__tab-panel';
+const defaultProps = {
+ className: DEFAULT_CLASS,
+ forceRender: false,
+ selectedClassName: `${DEFAULT_CLASS}--selected`,
+};
+const propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object,
+ ]),
+ forceRender: PropTypes.bool,
+ id: PropTypes.string, // private
+ selected: PropTypes.bool, // private
+ selectedClassName: PropTypes.string,
+ tabId: PropTypes.string, // private
+};
+const TabPanel = (props) => {
+ const {
+ children,
+ className,
+ forceRender,
+ id,
+ selected,
+ selectedClassName,
+ tabId,
+ ...attributes
+ } = props;
-export default class TabPanel extends Component {
- static defaultProps = {
- className: DEFAULT_CLASS,
- forceRender: false,
- selectedClassName: `${DEFAULT_CLASS}--selected`,
- };
-
- static propTypes = {
- children: PropTypes.node,
- className: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.array,
- PropTypes.object,
- ]),
- forceRender: PropTypes.bool,
- id: PropTypes.string, // private
- selected: PropTypes.bool, // private
- selectedClassName: PropTypes.string,
- tabId: PropTypes.string, // private
- };
-
- render() {
- const {
- children,
- className,
- forceRender,
- id,
- selected,
- selectedClassName,
- tabId,
- ...attributes
- } = this.props;
-
- return (
-
- {forceRender || selected ? children : null}
-
- );
- }
-}
+ return (
+
+ {forceRender || selected ? children : null}
+
+ );
+};
TabPanel.tabsRole = 'TabPanel';
+TabPanel.propTypes = propTypes;
+TabPanel.defaultProps = defaultProps;
+export default TabPanel;
diff --git a/src/components/Tabs.js b/src/components/Tabs.js
index 1076c0364a..f374c66cfc 100644
--- a/src/components/Tabs.js
+++ b/src/components/Tabs.js
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
-import React, { Component } from 'react';
+import React, { useEffect, useState } from 'react';
import {
childrenPropType,
onSelectPropType,
@@ -10,55 +10,85 @@ import { getTabsCount } from '../helpers/count';
const MODE_CONTROLLED = 0;
const MODE_UNCONTROLLED = 1;
-
-export default class Tabs extends Component {
- static defaultProps = {
- defaultFocus: false,
- forceRenderTabPanel: false,
- selectedIndex: null,
- defaultIndex: null,
- environment: null,
- disableUpDownKeys: false,
- };
-
- static propTypes = {
- children: childrenPropType,
- direction: PropTypes.oneOf(['rtl', 'ltr']),
- className: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.array,
- PropTypes.object,
- ]),
- defaultFocus: PropTypes.bool,
- defaultIndex: PropTypes.number,
- disabledTabClassName: PropTypes.string,
- disableUpDownKeys: PropTypes.bool,
- domRef: PropTypes.func,
- forceRenderTabPanel: PropTypes.bool,
- onSelect: onSelectPropType,
- selectedIndex: selectedIndexPropType,
- selectedTabClassName: PropTypes.string,
- selectedTabPanelClassName: PropTypes.string,
- environment: PropTypes.object,
- };
-
- constructor(props) {
- super(props);
-
- this.state = Tabs.copyPropsToState(this.props, {}, props.defaultFocus);
+const propTypes = {
+ children: childrenPropType,
+ direction: PropTypes.oneOf(['rtl', 'ltr']),
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object,
+ ]),
+ defaultFocus: PropTypes.bool,
+ defaultIndex: PropTypes.number,
+ disabledTabClassName: PropTypes.string,
+ disableUpDownKeys: PropTypes.bool,
+ domRef: PropTypes.func,
+ forceRenderTabPanel: PropTypes.bool,
+ onSelect: onSelectPropType,
+ selectedIndex: selectedIndexPropType,
+ selectedTabClassName: PropTypes.string,
+ selectedTabPanelClassName: PropTypes.string,
+ environment: PropTypes.object,
+};
+const defaultProps = {
+ defaultFocus: false,
+ forceRenderTabPanel: false,
+ selectedIndex: null,
+ defaultIndex: null,
+ environment: null,
+ disableUpDownKeys: false,
+};
+
+const getModeFromProps = (props) => {
+ return props.selectedIndex === null ? MODE_UNCONTROLLED : MODE_CONTROLLED;
+};
+
+const checkForIllegalModeChange = (props, mode) => {
+ if (
+ process.env.NODE_ENV !== 'production' &&
+ mode != undefined &&
+ mode !== getModeFromProps(props)
+ ) {
+ throw new Error(
+ `Switching between controlled mode (by using \`selectedIndex\`) and uncontrolled mode is not supported in \`Tabs\`.
+For more information about controlled and uncontrolled mode of react-tabs see https://github.com/reactjs/react-tabs#controlled-vs-uncontrolled-mode.`,
+ );
}
-
- static getDerivedStateFromProps(props, state) {
- return Tabs.copyPropsToState(props, state);
+};
+
+/**
+ * State:
+ * mode: Initialized only once from props and never changes
+ * selectedIndex: null if controlled mode, otherwise initialized with prop defaultIndex, changed on selection of tabs, has effect to ensure it never gets out of bound
+ * focus: Because we never remove focus from the Tabs this state is only used to indicate that we should focus the current tab.
+ * It is initialized from the prop defaultFocus, and after the first render it is reset back to false. Later it can become true again when using keys to navigate the tabs.
+ */
+const Tabs = (props) => {
+ const [focus, setFocus] = useState(props.defaultFocus);
+ const [mode] = useState(getModeFromProps(props));
+ const [selectedIndex, setSelectedIndex] = useState(
+ mode === MODE_UNCONTROLLED ? props.defaultIndex || 0 : null,
+ );
+
+ // Reset focus after initial render, see comment above
+ useEffect(() => {
+ setFocus(false);
+ }, []);
+
+ if (mode === MODE_UNCONTROLLED) {
+ // Ensure that we handle removed tabs and don't let selectedIndex get out of bounds
+ useEffect(() => {
+ if (selectedIndex != null) {
+ const maxTabIndex = Math.max(0, getTabsCount(props.children) - 1);
+ setSelectedIndex(Math.min(selectedIndex, maxTabIndex));
+ }
+ }, [getTabsCount(props.children)]);
}
- static getModeFromProps(props) {
- return props.selectedIndex === null ? MODE_UNCONTROLLED : MODE_CONTROLLED;
- }
+ checkForIllegalModeChange(props, mode);
- handleSelected = (index, last, event) => {
- const { onSelect } = this.props;
- const { mode } = this.state;
+ const handleSelected = (index, last, event) => {
+ const { onSelect } = props;
// Call change event handler
if (typeof onSelect === 'function') {
@@ -66,66 +96,32 @@ export default class Tabs extends Component {
if (onSelect(index, last, event) === false) return;
}
- const state = {
+ if (event.type === 'keydown') {
// Set focus if the change was triggered from the keyboard
- focus: event.type === 'keydown',
- };
+ setFocus(true);
+ }
if (mode === MODE_UNCONTROLLED) {
// Update selected index
- state.selectedIndex = index;
+ setSelectedIndex(index);
}
-
- this.setState(state);
};
- // preserve the existing selectedIndex from state.
- // If the state has not selectedIndex, default to the defaultIndex or 0
- static copyPropsToState(props, state, focus = false) {
- if (
- process.env.NODE_ENV !== 'production' &&
- state.mode !== undefined &&
- state.mode !== Tabs.getModeFromProps(props)
- ) {
- throw new Error(
- `Switching between controlled mode (by using \`selectedIndex\`) and uncontrolled mode is not supported in \`Tabs\`.
-For more information about controlled and uncontrolled mode of react-tabs see https://github.com/reactjs/react-tabs#controlled-vs-uncontrolled-mode.`,
- );
- }
-
- const newState = {
- focus,
- mode: Tabs.getModeFromProps(props),
- };
+ let newProps = { ...props };
+ const { children } = props;
- if (newState.mode === MODE_UNCONTROLLED) {
- const maxTabIndex = Math.max(0, getTabsCount(props.children) - 1);
- let selectedIndex = null;
+ newProps.focus = focus;
+ newProps.onSelect = handleSelected;
- if (state.selectedIndex != null) {
- selectedIndex = Math.min(state.selectedIndex, maxTabIndex);
- } else {
- selectedIndex = props.defaultIndex || 0;
- }
- newState.selectedIndex = selectedIndex;
- }
-
- return newState;
+ if (selectedIndex != null) {
+ newProps.selectedIndex = selectedIndex;
}
-
- render() {
- const { children, defaultIndex, defaultFocus, ...props } = this.props;
- const { focus, selectedIndex } = this.state;
-
- props.focus = focus;
- props.onSelect = this.handleSelected;
-
- if (selectedIndex != null) {
- props.selectedIndex = selectedIndex;
- }
-
- return {children};
- }
-}
-
+ delete newProps.defaultFocus;
+ delete newProps.defaultIndex;
+ return {children};
+};
+Tabs.propTypes = propTypes;
+Tabs.defaultProps = defaultProps;
Tabs.tabsRole = 'Tabs';
+
+export default Tabs;
diff --git a/src/components/UncontrolledTabs.js b/src/components/UncontrolledTabs.js
index ca3cf81f2b..86eb0c01a1 100644
--- a/src/components/UncontrolledTabs.js
+++ b/src/components/UncontrolledTabs.js
@@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
-import React, { cloneElement, Component } from 'react';
+import React, { cloneElement, useRef } from 'react';
import cx from 'clsx';
import uuid from '../helpers/uuid';
import { childrenPropType } from '../helpers/propTypes';
-import { getPanelsCount, getTabsCount } from '../helpers/count';
+import { getTabsCount as getTabsCountHelper } from '../helpers/count';
import { deepMap } from '../helpers/childrenDeepMap';
import { isTabList, isTabPanel, isTab } from '../helpers/elementTypes';
@@ -41,57 +41,61 @@ function determineCanUseActiveElement(environment) {
canUseActiveElement = false;
}
}
-export default class UncontrolledTabs extends Component {
- static defaultProps = {
- className: 'react-tabs',
- focus: false,
- };
-
- static propTypes = {
- children: childrenPropType,
- direction: PropTypes.oneOf(['rtl', 'ltr']),
- className: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.array,
- PropTypes.object,
- ]),
- disabledTabClassName: PropTypes.string,
- disableUpDownKeys: PropTypes.bool,
- domRef: PropTypes.func,
- focus: PropTypes.bool,
- forceRenderTabPanel: PropTypes.bool,
- onSelect: PropTypes.func.isRequired,
- selectedIndex: PropTypes.number.isRequired,
- selectedTabClassName: PropTypes.string,
- selectedTabPanelClassName: PropTypes.string,
- environment: PropTypes.object,
- };
-
- tabNodes = [];
-
- setSelected(index, event) {
+
+const defaultProps = {
+ className: 'react-tabs',
+ focus: false,
+};
+
+const propTypes = {
+ children: childrenPropType,
+ direction: PropTypes.oneOf(['rtl', 'ltr']),
+ className: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.array,
+ PropTypes.object,
+ ]),
+ disabledTabClassName: PropTypes.string,
+ disableUpDownKeys: PropTypes.bool,
+ domRef: PropTypes.func,
+ focus: PropTypes.bool,
+ forceRenderTabPanel: PropTypes.bool,
+ onSelect: PropTypes.func.isRequired,
+ selectedIndex: PropTypes.number.isRequired,
+ selectedTabClassName: PropTypes.string,
+ selectedTabPanelClassName: PropTypes.string,
+ environment: PropTypes.object,
+};
+
+const UncontrolledTabs = (props) => {
+ let tabNodes = useRef([]);
+ let tabIds = useRef([]);
+ let panelIds = useRef([]);
+ const ref = useRef();
+
+ function setSelected(index, event) {
// Check index boundary
- if (index < 0 || index >= this.getTabsCount()) return;
+ if (index < 0 || index >= getTabsCount()) return;
- const { onSelect, selectedIndex } = this.props;
+ const { onSelect, selectedIndex } = props;
// Call change event handler
onSelect(index, selectedIndex, event);
}
- getNextTab(index) {
- const count = this.getTabsCount();
+ function getNextTab(index) {
+ const count = getTabsCount();
// Look for non-disabled tab from index to the last tab on the right
for (let i = index + 1; i < count; i++) {
- if (!isTabDisabled(this.getTab(i))) {
+ if (!isTabDisabled(getTab(i))) {
return i;
}
}
// If no tab found, continue searching from first on left to index
for (let i = 0; i < index; i++) {
- if (!isTabDisabled(this.getTab(i))) {
+ if (!isTabDisabled(getTab(i))) {
return i;
}
}
@@ -100,20 +104,20 @@ export default class UncontrolledTabs extends Component {
return index;
}
- getPrevTab(index) {
+ function getPrevTab(index) {
let i = index;
// Look for non-disabled tab from index to first tab on the left
while (i--) {
- if (!isTabDisabled(this.getTab(i))) {
+ if (!isTabDisabled(getTab(i))) {
return i;
}
}
// If no tab found, continue searching from last tab on right to index
- i = this.getTabsCount();
+ i = getTabsCount();
while (i-- > index) {
- if (!isTabDisabled(this.getTab(i))) {
+ if (!isTabDisabled(getTab(i))) {
return i;
}
}
@@ -122,12 +126,12 @@ export default class UncontrolledTabs extends Component {
return index;
}
- getFirstTab() {
- const count = this.getTabsCount();
+ function getFirstTab() {
+ const count = getTabsCount();
// Look for non disabled tab from the first tab
for (let i = 0; i < count; i++) {
- if (!isTabDisabled(this.getTab(i))) {
+ if (!isTabDisabled(getTab(i))) {
return i;
}
}
@@ -135,12 +139,12 @@ export default class UncontrolledTabs extends Component {
return null;
}
- getLastTab() {
- let i = this.getTabsCount();
+ function getLastTab() {
+ let i = getTabsCount();
// Look for non disabled tab from the last tab
while (i--) {
- if (!isTabDisabled(this.getTab(i))) {
+ if (!isTabDisabled(getTab(i))) {
return i;
}
}
@@ -148,21 +152,16 @@ export default class UncontrolledTabs extends Component {
return null;
}
- getTabsCount() {
- const { children } = this.props;
- return getTabsCount(children);
- }
-
- getPanelsCount() {
- const { children } = this.props;
- return getPanelsCount(children);
+ function getTabsCount() {
+ const { children } = props;
+ return getTabsCountHelper(children);
}
- getTab(index) {
- return this.tabNodes[`tabs-${index}`];
+ function getTab(index) {
+ return tabNodes.current[`tabs-${index}`];
}
- getChildren() {
+ function getChildren() {
let index = 0;
const {
children,
@@ -173,18 +172,18 @@ export default class UncontrolledTabs extends Component {
selectedTabClassName,
selectedTabPanelClassName,
environment,
- } = this.props;
+ } = props;
- this.tabIds = this.tabIds || [];
- this.panelIds = this.panelIds || [];
- let diff = this.tabIds.length - this.getTabsCount();
+ tabIds.current = tabIds.current || [];
+ panelIds.current = panelIds.current || [];
+ let diff = tabIds.current.length - getTabsCount();
// Add ids if new tabs have been added
// Don't bother removing ids, just keep them in case they are added again
// This is more efficient, and keeps the uuid counter under control
while (diff++ < 0) {
- this.tabIds.push(uuid());
- this.panelIds.push(uuid());
+ tabIds.current.push(uuid());
+ panelIds.current.push(uuid());
}
// Map children to dynamically setup refs
@@ -210,7 +209,7 @@ export default class UncontrolledTabs extends Component {
const env =
environment ||
(typeof window !== 'undefined' ? window : undefined);
- return env && env.document.activeElement === this.getTab(i);
+ return env && env.document.activeElement === getTab(i);
});
}
@@ -221,10 +220,10 @@ export default class UncontrolledTabs extends Component {
const props = {
tabRef: (node) => {
- this.tabNodes[key] = node;
+ tabNodes.current[key] = node;
},
- id: this.tabIds[listIndex],
- panelId: this.panelIds[listIndex],
+ id: tabIds.current[listIndex],
+ panelId: panelIds.current[listIndex],
selected,
focus: selected && (focus || wasTabFocused),
};
@@ -241,8 +240,8 @@ export default class UncontrolledTabs extends Component {
});
} else if (isTabPanel(child)) {
const props = {
- id: this.panelIds[index],
- tabId: this.tabIds[index],
+ id: panelIds.current[index],
+ tabId: tabIds.current[index],
selected: selectedIndex === index,
};
@@ -259,45 +258,45 @@ export default class UncontrolledTabs extends Component {
});
}
- handleKeyDown = (e) => {
- const { direction, disableUpDownKeys } = this.props;
- if (this.isTabFromContainer(e.target)) {
- let { selectedIndex: index } = this.props;
+ function handleKeyDown(e) {
+ const { direction, disableUpDownKeys } = props;
+ if (isTabFromContainer(e.target)) {
+ let { selectedIndex: index } = props;
let preventDefault = false;
let useSelectedIndex = false;
if (e.keyCode === 32 || e.keyCode === 13) {
preventDefault = true;
useSelectedIndex = false;
- this.handleClick(e);
+ handleClick(e);
}
if (e.keyCode === 37 || (!disableUpDownKeys && e.keyCode === 38)) {
// Select next tab to the left, validate if up arrow is not disabled
if (direction === 'rtl') {
- index = this.getNextTab(index);
+ index = getNextTab(index);
} else {
- index = this.getPrevTab(index);
+ index = getPrevTab(index);
}
preventDefault = true;
useSelectedIndex = true;
} else if (e.keyCode === 39 || (!disableUpDownKeys && e.keyCode === 40)) {
// Select next tab to the right, validate if down arrow is not disabled
if (direction === 'rtl') {
- index = this.getPrevTab(index);
+ index = getPrevTab(index);
} else {
- index = this.getNextTab(index);
+ index = getNextTab(index);
}
preventDefault = true;
useSelectedIndex = true;
} else if (e.keyCode === 35) {
// Select last tab (End key)
- index = this.getLastTab();
+ index = getLastTab();
preventDefault = true;
useSelectedIndex = true;
} else if (e.keyCode === 36) {
// Select first tab (Home key)
- index = this.getFirstTab();
+ index = getFirstTab();
preventDefault = true;
useSelectedIndex = true;
}
@@ -309,15 +308,15 @@ export default class UncontrolledTabs extends Component {
// Only use the selected index in the state if we're not using the tabbed index
if (useSelectedIndex) {
- this.setSelected(index, e);
+ setSelected(index, e);
}
}
- };
+ }
- handleClick = (e) => {
+ function handleClick(e) {
let node = e.target;
do {
- if (this.isTabFromContainer(node)) {
+ if (isTabFromContainer(node)) {
if (isTabDisabled(node)) {
return;
}
@@ -326,18 +325,18 @@ export default class UncontrolledTabs extends Component {
.call(node.parentNode.children)
.filter(isTabNode)
.indexOf(node);
- this.setSelected(index, e);
+ setSelected(index, e);
return;
}
} while ((node = node.parentNode) != null);
- };
+ }
/**
* Determine if a node from event.target is a Tab element for the current Tabs container.
* If the clicked element is not a Tab, it returns false.
* If it finds another Tabs container between the Tab and `this`, it returns false.
*/
- isTabFromContainer(node) {
+ function isTabFromContainer(node) {
// return immediately if the clicked element is not a Tab.
if (!isTabNode(node)) {
return false;
@@ -346,7 +345,7 @@ export default class UncontrolledTabs extends Component {
// Check if the first occurrence of a Tabs container is `this` one.
let nodeAncestor = node.parentElement;
do {
- if (nodeAncestor === this.node) return true;
+ if (nodeAncestor === ref.current) return true;
if (nodeAncestor.getAttribute('data-rttabs')) break;
nodeAncestor = nodeAncestor.parentElement;
@@ -354,39 +353,37 @@ export default class UncontrolledTabs extends Component {
return false;
}
-
- render() {
- // Delete all known props, so they don't get added to DOM
- const {
- children, // unused
- className,
- disabledTabClassName, // unused
- domRef,
- focus, // unused
- forceRenderTabPanel, // unused
- onSelect, // unused
- selectedIndex, // unused
- selectedTabClassName, // unused
- selectedTabPanelClassName, // unused
- environment, // unused
- disableUpDownKeys, // unused
- ...attributes
- } = this.props;
-
- return (
- {
- this.node = node;
- if (domRef) domRef(node);
- }}
- data-rttabs
- >
- {this.getChildren()}
-
- );
- }
-}
+ const {
+ children, // unused
+ className,
+ disabledTabClassName, // unused
+ domRef,
+ focus, // unused
+ forceRenderTabPanel, // unused
+ onSelect, // unused
+ selectedIndex, // unused
+ selectedTabClassName, // unused
+ selectedTabPanelClassName, // unused
+ environment, // unused
+ disableUpDownKeys, // unused
+ ...attributes
+ } = props;
+ return (
+ {
+ ref.current = node;
+ if (domRef) domRef(node);
+ }}
+ data-rttabs
+ >
+ {getChildren()}
+
+ );
+};
+UncontrolledTabs.defaultProps = defaultProps;
+UncontrolledTabs.propTypes = propTypes;
+export default UncontrolledTabs;
diff --git a/src/components/__tests__/Tabs-test.js b/src/components/__tests__/Tabs-test.js
index 2065f87e41..ff39c47f07 100644
--- a/src/components/__tests__/Tabs-test.js
+++ b/src/components/__tests__/Tabs-test.js
@@ -264,6 +264,35 @@ describe('', () => {
).toThrowErrorMatchingSnapshot();
});
+ test('should throw when mode of component changes', () => {
+ const { rerender } = render(
+ {}}>
+
+ Foo
+ Foo2
+
+ Foo
+ Foo2
+ ,
+ );
+ try {
+ rerender(
+ {}}>
+
+ Foo
+ Foo2
+
+ Foo
+ Foo2
+ ,
+ );
+ } catch (e) {
+ expect(e.message).toContain(
+ 'Switching between controlled mode (by using `selectedIndex`) and uncontrolled mode is not supported in `Tabs`.',
+ );
+ }
+ });
+
test('should result with warning when defaultIndex and selectedIndex set', () => {
expect(() =>
render(
diff --git a/src/helpers/count.js b/src/helpers/count.js
index fc24040096..6334cbef38 100644
--- a/src/helpers/count.js
+++ b/src/helpers/count.js
@@ -1,5 +1,5 @@
import { deepForEach } from './childrenDeepMap';
-import { isTab, isTabPanel } from './elementTypes';
+import { isTab } from './elementTypes';
export function getTabsCount(children) {
let tabCount = 0;
@@ -9,12 +9,3 @@ export function getTabsCount(children) {
return tabCount;
}
-
-export function getPanelsCount(children) {
- let panelCount = 0;
- deepForEach(children, (child) => {
- if (isTabPanel(child)) panelCount++;
- });
-
- return panelCount;
-}