From b2159c91b0a8836a25a5cf0c9cd4b57e929ceece Mon Sep 17 00:00:00 2001 From: portableCoder <59962523+portableCoder@users.noreply.github.com> Date: Tue, 15 Feb 2022 19:10:25 +0530 Subject: [PATCH] feat: Updated all the class components to newer functional components --- src/components/Tab.js | 165 +++++++++-------- src/components/TabList.js | 47 +++-- src/components/TabPanel.js | 96 +++++----- src/components/Tabs.js | 192 ++++++++++---------- src/components/UncontrolledTabs.js | 247 +++++++++++++------------- src/components/__tests__/Tabs-test.js | 29 +++ src/helpers/count.js | 11 +- 7 files changed, 396 insertions(+), 391 deletions(-) diff --git a/src/components/Tab.js b/src/components/Tab.js index 256070a50..8c0c5dc67 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 bac4f3fe3..425bc8317 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 894de1f85..b0c325d2c 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 1076c0364..f374c66cf 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 ca3cf81f2..86eb0c01a 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 2065f87e4..ff39c47f0 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 fc2404009..6334cbef3 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; -}