Skip to content

Commit 487326c

Browse files
committed
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.
1 parent a799fb7 commit 487326c

16 files changed

+240
-427
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"react-component"
4949
],
5050
"peerDependencies": {
51-
"react": "^16.8.0 || ^17.0.0-0 || ^18.0.0"
51+
"react": "^18.0.0"
5252
},
5353
"devDependencies": {
5454
"@babel/cli": "7.17.6",

src/components/Tab.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ import React, { useEffect, useRef } from 'react';
33
import cx from 'clsx';
44

55
const DEFAULT_CLASS = 'react-tabs__tab';
6-
const DEFAULT_PROPS = {
6+
const defaultProps = {
77
className: DEFAULT_CLASS,
88
disabledClassName: `${DEFAULT_CLASS}--disabled`,
99
focus: false,
1010
id: null,
11-
panelId: null,
1211
selected: false,
1312
selectedClassName: `${DEFAULT_CLASS}--selected`,
1413
};
@@ -29,7 +28,6 @@ const propTypes = {
2928
disabledClassName: PropTypes.string,
3029
focus: PropTypes.bool, // private
3130
id: PropTypes.string, // private
32-
panelId: PropTypes.string, // private
3331
selected: PropTypes.bool, // private
3432
selectedClassName: PropTypes.string,
3533
tabRef: PropTypes.func,
@@ -44,7 +42,6 @@ const Tab = (props) => {
4442
disabledClassName,
4543
focus,
4644
id,
47-
panelId,
4845
selected,
4946
selectedClassName,
5047
tabIndex,
@@ -70,10 +67,10 @@ const Tab = (props) => {
7067
if (tabRef) tabRef(node);
7168
}}
7269
role="tab"
73-
id={id}
70+
id={`tab${id}`}
7471
aria-selected={selected ? 'true' : 'false'}
7572
aria-disabled={disabled ? 'true' : 'false'}
76-
aria-controls={panelId}
73+
aria-controls={`panel${id}`}
7774
tabIndex={tabIndex || (selected ? '0' : null)}
7875
data-rttab
7976
>
@@ -84,5 +81,5 @@ const Tab = (props) => {
8481
Tab.propTypes = propTypes;
8582

8683
Tab.tabsRole = 'Tab';
87-
Tab.defaultProps = DEFAULT_PROPS;
84+
Tab.defaultProps = defaultProps;
8885
export default Tab;

src/components/TabPanel.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ const propTypes = {
1919
id: PropTypes.string, // private
2020
selected: PropTypes.bool, // private
2121
selectedClassName: PropTypes.string,
22-
tabId: PropTypes.string, // private
2322
};
2423
const TabPanel = (props) => {
2524
const {
@@ -29,7 +28,6 @@ const TabPanel = (props) => {
2928
id,
3029
selected,
3130
selectedClassName,
32-
tabId,
3331
...attributes
3432
} = props;
3533

@@ -40,8 +38,8 @@ const TabPanel = (props) => {
4038
[selectedClassName]: selected,
4139
})}
4240
role="tabpanel"
43-
id={id}
44-
aria-labelledby={tabId}
41+
id={`panel${id}`}
42+
aria-labelledby={`tab${id}`}
4543
>
4644
{forceRender || selected ? children : null}
4745
</div>

src/components/UncontrolledTabs.js

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import PropTypes from 'prop-types';
2-
import React, { cloneElement, useRef } from 'react';
2+
import React, { cloneElement, useRef, useId } from 'react';
33
import cx from 'clsx';
4-
import uuid from '../helpers/uuid';
54
import { childrenPropType } from '../helpers/propTypes';
65
import { getTabsCount as getTabsCountHelper } from '../helpers/count';
76
import { deepMap } from '../helpers/childrenDeepMap';
@@ -71,7 +70,6 @@ const propTypes = {
7170
const UncontrolledTabs = (props) => {
7271
let tabNodes = useRef([]);
7372
let tabIds = useRef([]);
74-
let panelIds = useRef([]);
7573
const ref = useRef();
7674

7775
function setSelected(index, event) {
@@ -180,15 +178,14 @@ const UncontrolledTabs = (props) => {
180178
} = props;
181179

182180
tabIds.current = tabIds.current || [];
183-
panelIds.current = panelIds.current || [];
184181
let diff = tabIds.current.length - getTabsCount();
185182

186183
// Add ids if new tabs have been added
187184
// Don't bother removing ids, just keep them in case they are added again
188185
// This is more efficient, and keeps the uuid counter under control
186+
const id = useId();
189187
while (diff++ < 0) {
190-
tabIds.current.push(uuid());
191-
panelIds.current.push(uuid());
188+
tabIds.current.push(`${id}${tabIds.current.length}`);
192189
}
193190

194191
// Map children to dynamically setup refs
@@ -225,7 +222,6 @@ const UncontrolledTabs = (props) => {
225222
tabNodes.current[key] = node;
226223
},
227224
id: tabIds.current[listIndex],
228-
panelId: panelIds.current[listIndex],
229225
selected,
230226
focus: selected && (focus || wasTabFocused),
231227
};
@@ -242,8 +238,7 @@ const UncontrolledTabs = (props) => {
242238
});
243239
} else if (isTabPanel(child)) {
244240
const props = {
245-
id: panelIds.current[index],
246-
tabId: tabIds.current[index],
241+
id: tabIds.current[index],
247242
selected: selectedIndex === index,
248243
};
249244

src/components/__tests__/Tab-test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ describe('<Tab />', () => {
2525

2626
it('should support being selected', () => {
2727
expectToMatchSnapshot(
28-
<Tab selected id="abcd" panelId="1234">
28+
<Tab selected id="abcd">
2929
Hello
3030
</Tab>,
3131
);

src/components/__tests__/TabList-test.js

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ import TabPanel from '../TabPanel';
66
import Tabs from '../Tabs';
77
import { TabListWrapper, TabWrapper } from './helpers/higherOrder';
88

9+
jest.mock('react', () => {
10+
const originalModule = jest.requireActual('react');
11+
12+
return {
13+
...originalModule,
14+
useId: () => ':r0:',
15+
};
16+
});
17+
918
function expectToMatchSnapshot(component) {
1019
expect(renderer.create(component).toJSON()).toMatchSnapshot();
1120
}

src/components/__tests__/TabPanel-test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ describe('<TabPanel />', () => {
3333

3434
it('should support being selected', () => {
3535
expectToMatchSnapshot(
36-
<TabPanel selected id="abcd" tabId="1234">
36+
<TabPanel selected id="abcd">
3737
Hola
3838
</TabPanel>,
3939
);
4040
});
4141

4242
it('should support being selected with custom class name', () => {
4343
expectToMatchSnapshot(
44-
<TabPanel selected id="abcd" tabId="1234" selectedClassName="selected">
44+
<TabPanel selected id="abcd" selectedClassName="selected">
4545
Hola
4646
</TabPanel>,
4747
);

src/components/__tests__/Tabs-errors-test.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@ import Tab from '../Tab';
55
import TabList from '../TabList';
66
import TabPanel from '../TabPanel';
77
import Tabs from '../Tabs';
8-
import { reset as resetIdCounter } from '../../helpers/uuid';
8+
9+
jest.mock('react', () => {
10+
const originalModule = jest.requireActual('react');
11+
12+
return {
13+
...originalModule,
14+
useId: () => ':r0:',
15+
};
16+
});
917

1018
describe('<Tabs />', () => {
1119
let consoleErrorMock;
@@ -21,7 +29,6 @@ describe('<Tabs />', () => {
2129
}
2230

2331
beforeEach(() => {
24-
resetIdCounter();
2532
consoleErrorMock = jest.spyOn(console, 'error').mockImplementation();
2633
});
2734

src/components/__tests__/Tabs-node-test.js

+9-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ import Tab from '../Tab';
66
import TabList from '../TabList';
77
import TabPanel from '../TabPanel';
88
import Tabs from '../Tabs';
9-
import { reset as resetIdCounter } from '../../helpers/uuid';
9+
10+
jest.mock('react', () => {
11+
const originalModule = jest.requireActual('react');
12+
13+
return {
14+
...originalModule,
15+
useId: () => ':r0:',
16+
};
17+
});
1018

1119
function createTabs(props = {}) {
1220
return (
@@ -28,15 +36,6 @@ function createTabs(props = {}) {
2836
}
2937

3038
describe('ServerSide <Tabs />', () => {
31-
beforeEach(() => resetIdCounter());
32-
33-
beforeAll(() => {
34-
// eslint-disable-next-line no-console
35-
console.error = (error) => {
36-
throw new Error(error);
37-
};
38-
});
39-
4039
test('does not crash in node environments', () => {
4140
expect(() => createTabs()).not.toThrow();
4241
});

src/components/__tests__/Tabs-test.js

+9-13
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,21 @@ import Tab from '../Tab';
66
import TabList from '../TabList';
77
import TabPanel from '../TabPanel';
88
import Tabs from '../Tabs';
9-
import { reset as resetIdCounter } from '../../helpers/uuid';
109
import {
1110
TabListWrapper,
1211
TabWrapper,
1312
TabPanelWrapper,
1413
} from './helpers/higherOrder';
1514

15+
jest.mock('react', () => {
16+
const originalModule = jest.requireActual('react');
17+
18+
return {
19+
...originalModule,
20+
useId: () => ':r0:',
21+
};
22+
});
23+
1624
function expectToMatchSnapshot(component) {
1725
expect(render(component).container.firstChild).toMatchSnapshot();
1826
}
@@ -47,8 +55,6 @@ function assertTabSelected(tabNo, node = screen) {
4755
}
4856

4957
describe('<Tabs />', () => {
50-
beforeEach(() => resetIdCounter());
51-
5258
describe('props', () => {
5359
test('should have sane defaults', () => {
5460
expectToMatchSnapshot(createTabs());
@@ -98,16 +104,6 @@ describe('<Tabs />', () => {
98104
});
99105
});
100106

101-
describe('child props', () => {
102-
test('should reset ids correctly', () => {
103-
expectToMatchSnapshot(createTabs());
104-
105-
resetIdCounter();
106-
107-
expectToMatchSnapshot(createTabs());
108-
});
109-
});
110-
111107
describe('interaction', () => {
112108
describe('mouse', () => {
113109
test('should update selectedIndex when clicked', async () => {

0 commit comments

Comments
 (0)