From 487326c02f5316887dc4b27ac39e6ef69cdd2c03 Mon Sep 17 00:00:00 2001 From: Daniel Tschinder <231804+danez@users.noreply.github.com> Date: Sat, 2 Apr 2022 22:34:54 +0000 Subject: [PATCH] feat: Use new `useId` hook from react 18 BREAKING CHANGE: React version 18 or newer is now required. BREAKING CHANGE: `resetIdCounter` was removed as it is not necessary anymore. Ensure you remove any calls to it from your code. --- package.json | 2 +- src/components/Tab.js | 11 +- src/components/TabPanel.js | 6 +- src/components/UncontrolledTabs.js | 13 +- src/components/__tests__/Tab-test.js | 2 +- src/components/__tests__/TabList-test.js | 9 + src/components/__tests__/TabPanel-test.js | 4 +- src/components/__tests__/Tabs-errors-test.js | 11 +- src/components/__tests__/Tabs-node-test.js | 19 +- src/components/__tests__/Tabs-test.js | 22 +- .../__tests__/__snapshots__/Tab-test.js.snap | 40 +- .../__snapshots__/TabList-test.js.snap | 64 +-- .../__snapshots__/TabPanel-test.js.snap | 22 +- .../__tests__/__snapshots__/Tabs-test.js.snap | 432 +++++------------- src/helpers/uuid.js | 9 - src/index.js | 1 - 16 files changed, 240 insertions(+), 427 deletions(-) delete mode 100644 src/helpers/uuid.js diff --git a/package.json b/package.json index b90465f79..89d954d08 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "react-component" ], "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-0 || ^18.0.0" + "react": "^18.0.0" }, "devDependencies": { "@babel/cli": "7.17.6", diff --git a/src/components/Tab.js b/src/components/Tab.js index 5f8ad0c44..265ec5f55 100644 --- a/src/components/Tab.js +++ b/src/components/Tab.js @@ -3,12 +3,11 @@ import React, { useEffect, useRef } from 'react'; import cx from 'clsx'; const DEFAULT_CLASS = 'react-tabs__tab'; -const DEFAULT_PROPS = { +const defaultProps = { className: DEFAULT_CLASS, disabledClassName: `${DEFAULT_CLASS}--disabled`, focus: false, id: null, - panelId: null, selected: false, selectedClassName: `${DEFAULT_CLASS}--selected`, }; @@ -29,7 +28,6 @@ const propTypes = { 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, @@ -44,7 +42,6 @@ const Tab = (props) => { disabledClassName, focus, id, - panelId, selected, selectedClassName, tabIndex, @@ -70,10 +67,10 @@ const Tab = (props) => { if (tabRef) tabRef(node); }} role="tab" - id={id} + id={`tab${id}`} aria-selected={selected ? 'true' : 'false'} aria-disabled={disabled ? 'true' : 'false'} - aria-controls={panelId} + aria-controls={`panel${id}`} tabIndex={tabIndex || (selected ? '0' : null)} data-rttab > @@ -84,5 +81,5 @@ const Tab = (props) => { Tab.propTypes = propTypes; Tab.tabsRole = 'Tab'; -Tab.defaultProps = DEFAULT_PROPS; +Tab.defaultProps = defaultProps; export default Tab; diff --git a/src/components/TabPanel.js b/src/components/TabPanel.js index b0c325d2c..db6e49cff 100644 --- a/src/components/TabPanel.js +++ b/src/components/TabPanel.js @@ -19,7 +19,6 @@ const propTypes = { id: PropTypes.string, // private selected: PropTypes.bool, // private selectedClassName: PropTypes.string, - tabId: PropTypes.string, // private }; const TabPanel = (props) => { const { @@ -29,7 +28,6 @@ const TabPanel = (props) => { id, selected, selectedClassName, - tabId, ...attributes } = props; @@ -40,8 +38,8 @@ const TabPanel = (props) => { [selectedClassName]: selected, })} role="tabpanel" - id={id} - aria-labelledby={tabId} + id={`panel${id}`} + aria-labelledby={`tab${id}`} > {forceRender || selected ? children : null} diff --git a/src/components/UncontrolledTabs.js b/src/components/UncontrolledTabs.js index 44ef38ccd..c90e3e6bd 100644 --- a/src/components/UncontrolledTabs.js +++ b/src/components/UncontrolledTabs.js @@ -1,7 +1,6 @@ import PropTypes from 'prop-types'; -import React, { cloneElement, useRef } from 'react'; +import React, { cloneElement, useRef, useId } from 'react'; import cx from 'clsx'; -import uuid from '../helpers/uuid'; import { childrenPropType } from '../helpers/propTypes'; import { getTabsCount as getTabsCountHelper } from '../helpers/count'; import { deepMap } from '../helpers/childrenDeepMap'; @@ -71,7 +70,6 @@ const propTypes = { const UncontrolledTabs = (props) => { let tabNodes = useRef([]); let tabIds = useRef([]); - let panelIds = useRef([]); const ref = useRef(); function setSelected(index, event) { @@ -180,15 +178,14 @@ const UncontrolledTabs = (props) => { } = props; 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 + const id = useId(); while (diff++ < 0) { - tabIds.current.push(uuid()); - panelIds.current.push(uuid()); + tabIds.current.push(`${id}${tabIds.current.length}`); } // Map children to dynamically setup refs @@ -225,7 +222,6 @@ const UncontrolledTabs = (props) => { tabNodes.current[key] = node; }, id: tabIds.current[listIndex], - panelId: panelIds.current[listIndex], selected, focus: selected && (focus || wasTabFocused), }; @@ -242,8 +238,7 @@ const UncontrolledTabs = (props) => { }); } else if (isTabPanel(child)) { const props = { - id: panelIds.current[index], - tabId: tabIds.current[index], + id: tabIds.current[index], selected: selectedIndex === index, }; diff --git a/src/components/__tests__/Tab-test.js b/src/components/__tests__/Tab-test.js index e159e1d62..6b3d00e6a 100644 --- a/src/components/__tests__/Tab-test.js +++ b/src/components/__tests__/Tab-test.js @@ -25,7 +25,7 @@ describe('', () => { it('should support being selected', () => { expectToMatchSnapshot( - + Hello , ); diff --git a/src/components/__tests__/TabList-test.js b/src/components/__tests__/TabList-test.js index 9e9b4d3a4..ed9552c14 100644 --- a/src/components/__tests__/TabList-test.js +++ b/src/components/__tests__/TabList-test.js @@ -6,6 +6,15 @@ import TabPanel from '../TabPanel'; import Tabs from '../Tabs'; import { TabListWrapper, TabWrapper } from './helpers/higherOrder'; +jest.mock('react', () => { + const originalModule = jest.requireActual('react'); + + return { + ...originalModule, + useId: () => ':r0:', + }; +}); + function expectToMatchSnapshot(component) { expect(renderer.create(component).toJSON()).toMatchSnapshot(); } diff --git a/src/components/__tests__/TabPanel-test.js b/src/components/__tests__/TabPanel-test.js index 9a21e9146..b8a7a8099 100644 --- a/src/components/__tests__/TabPanel-test.js +++ b/src/components/__tests__/TabPanel-test.js @@ -33,7 +33,7 @@ describe('', () => { it('should support being selected', () => { expectToMatchSnapshot( - + Hola , ); @@ -41,7 +41,7 @@ describe('', () => { it('should support being selected with custom class name', () => { expectToMatchSnapshot( - + Hola , ); diff --git a/src/components/__tests__/Tabs-errors-test.js b/src/components/__tests__/Tabs-errors-test.js index ddc0f3703..2f6a75acf 100644 --- a/src/components/__tests__/Tabs-errors-test.js +++ b/src/components/__tests__/Tabs-errors-test.js @@ -5,7 +5,15 @@ import Tab from '../Tab'; import TabList from '../TabList'; import TabPanel from '../TabPanel'; import Tabs from '../Tabs'; -import { reset as resetIdCounter } from '../../helpers/uuid'; + +jest.mock('react', () => { + const originalModule = jest.requireActual('react'); + + return { + ...originalModule, + useId: () => ':r0:', + }; +}); describe('', () => { let consoleErrorMock; @@ -21,7 +29,6 @@ describe('', () => { } beforeEach(() => { - resetIdCounter(); consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(); }); diff --git a/src/components/__tests__/Tabs-node-test.js b/src/components/__tests__/Tabs-node-test.js index cf94e77fc..7e608936d 100644 --- a/src/components/__tests__/Tabs-node-test.js +++ b/src/components/__tests__/Tabs-node-test.js @@ -6,7 +6,15 @@ import Tab from '../Tab'; import TabList from '../TabList'; import TabPanel from '../TabPanel'; import Tabs from '../Tabs'; -import { reset as resetIdCounter } from '../../helpers/uuid'; + +jest.mock('react', () => { + const originalModule = jest.requireActual('react'); + + return { + ...originalModule, + useId: () => ':r0:', + }; +}); function createTabs(props = {}) { return ( @@ -28,15 +36,6 @@ function createTabs(props = {}) { } describe('ServerSide ', () => { - beforeEach(() => resetIdCounter()); - - beforeAll(() => { - // eslint-disable-next-line no-console - console.error = (error) => { - throw new Error(error); - }; - }); - test('does not crash in node environments', () => { expect(() => createTabs()).not.toThrow(); }); diff --git a/src/components/__tests__/Tabs-test.js b/src/components/__tests__/Tabs-test.js index eed73ee00..a993e82c6 100644 --- a/src/components/__tests__/Tabs-test.js +++ b/src/components/__tests__/Tabs-test.js @@ -6,13 +6,21 @@ import Tab from '../Tab'; import TabList from '../TabList'; import TabPanel from '../TabPanel'; import Tabs from '../Tabs'; -import { reset as resetIdCounter } from '../../helpers/uuid'; import { TabListWrapper, TabWrapper, TabPanelWrapper, } from './helpers/higherOrder'; +jest.mock('react', () => { + const originalModule = jest.requireActual('react'); + + return { + ...originalModule, + useId: () => ':r0:', + }; +}); + function expectToMatchSnapshot(component) { expect(render(component).container.firstChild).toMatchSnapshot(); } @@ -47,8 +55,6 @@ function assertTabSelected(tabNo, node = screen) { } describe('', () => { - beforeEach(() => resetIdCounter()); - describe('props', () => { test('should have sane defaults', () => { expectToMatchSnapshot(createTabs()); @@ -98,16 +104,6 @@ describe('', () => { }); }); - describe('child props', () => { - test('should reset ids correctly', () => { - expectToMatchSnapshot(createTabs()); - - resetIdCounter(); - - expectToMatchSnapshot(createTabs()); - }); - }); - describe('interaction', () => { describe('mouse', () => { test('should update selectedIndex when clicked', async () => { diff --git a/src/components/__tests__/__snapshots__/Tab-test.js.snap b/src/components/__tests__/__snapshots__/Tab-test.js.snap index f297ca0e3..ebf635d1b 100644 --- a/src/components/__tests__/__snapshots__/Tab-test.js.snap +++ b/src/components/__tests__/__snapshots__/Tab-test.js.snap @@ -2,12 +2,12 @@ exports[` override the tabIndex if it was provided 1`] = `
Foo
@@ -72,24 +72,24 @@ exports[` should display the custom classnames for selected and disab role="tablist" >
Foo
@@ -125,24 +125,24 @@ exports[` should display the custom classnames for selected and disab role="tablist" >
Foo
@@ -200,24 +200,24 @@ exports[` should retain the default classnames for active and disable role="tablist" >
Foo
diff --git a/src/components/__tests__/__snapshots__/TabPanel-test.js.snap b/src/components/__tests__/__snapshots__/TabPanel-test.js.snap index 303a902f2..4c4f1d126 100644 --- a/src/components/__tests__/__snapshots__/TabPanel-test.js.snap +++ b/src/components/__tests__/__snapshots__/TabPanel-test.js.snap @@ -2,43 +2,55 @@ exports[` should accept className 1`] = `
`; exports[` should allow for higher-order components 1`] = `
`; exports[` should have sane defaults 1`] = `
`; exports[` should not allow overriding all default properties 1`] = `
`; exports[` should pass through custom properties 1`] = `
`; exports[` should render when forced 1`] = `
Hola @@ -47,7 +59,9 @@ exports[` should render when forced 1`] = ` exports[` should render when selected 1`] = `
Hola @@ -56,9 +70,9 @@ exports[` should render when selected 1`] = ` exports[` should support being selected 1`] = `
Hola @@ -67,9 +81,9 @@ exports[` should support being selected 1`] = ` exports[` should support being selected with custom class name 1`] = `
Hola diff --git a/src/components/__tests__/__snapshots__/Tabs-test.js.snap b/src/components/__tests__/__snapshots__/Tabs-test.js.snap index e573a03b7..1dc196fd6 100644 --- a/src/components/__tests__/__snapshots__/Tabs-test.js.snap +++ b/src/components/__tests__/__snapshots__/Tabs-test.js.snap @@ -1,197 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` child props should reset ids correctly 1`] = ` -
-
    - - - - -
-
- Hello Tab1 -
-
-
-
-
-`; - -exports[` child props should reset ids correctly 2`] = ` -
-
    - - - - -
-
- Hello Tab1 -
-
-
-
-
-`; - exports[` performance should render all tabs if forceRenderTabPanel is true 1`] = `
performance should render all tabs if forceRenderTabPanel is t role="tablist" >
Hello Tab1
Hello Tab2
Hello Tab3
Hello Tab4 @@ -304,38 +112,38 @@ exports[` props should accept className 1`] = ` role="tablist" >
Hello Tab1
@@ -400,38 +208,38 @@ exports[` props should have sane defaults 1`] = ` role="tablist" >
Hello Tab1
@@ -496,37 +304,37 @@ exports[` props should honor negative defaultIndex prop 1`] = ` role="tablist" >
@@ -589,38 +397,38 @@ exports[` props should honor positive defaultIndex prop 1`] = ` role="tablist" >
Hello Tab2
@@ -685,41 +493,41 @@ exports[` should allow for higher order components 1`] = ` role="tablist" >
Foo
@@ -737,12 +545,12 @@ exports[` should allow string children 1`] = ` > Foo Foo
Hello Bar @@ -942,12 +750,12 @@ exports[` validation should gracefully render null 1`] = ` role="tablist" >
Content A @@ -975,21 +783,21 @@ exports[` validation should not throw a warning when wrong element is fo role="tablist" >