Skip to content

Commit ca039ec

Browse files
committed
chore(Tabs): Fix re-render bug
When tabs were clicked, I was seeing the page re-render except for the isNav tabs. Also added additional tests per CodeRabbit. Assisted-by: Cursor
1 parent c6d6d1c commit ca039ec

3 files changed

Lines changed: 118 additions & 18 deletions

File tree

packages/react-core/src/components/Tabs/Tabs.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,15 @@ class Tabs extends Component<TabsProps, TabsState> {
248248
tabContentRef: React.RefObject<any>
249249
) {
250250
const { shownKeys, initialActiveKey } = this.state;
251-
const { onSelect, defaultActiveKey } = this.props;
251+
const { onSelect, defaultActiveKey, component, isNav } = this.props;
252+
253+
if (event.currentTarget instanceof HTMLAnchorElement && event.currentTarget.hasAttribute('href')) {
254+
event.preventDefault();
255+
256+
if (canUseDOM && (component === TabsComponent.nav || isNav)) {
257+
window.history.pushState(null, '', event.currentTarget.href);
258+
}
259+
}
252260

253261
// if defaultActiveKey Tabs are uncontrolled, set new active key internally
254262
if (defaultActiveKey !== undefined) {

packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,58 @@ describe('hash-based nav selection', () => {
147147
expect(screen.getByRole('tab', { name: 'Tab item 1' })).toHaveAttribute('aria-selected', 'false');
148148
});
149149

150+
test('should select the nav tab that matches the current URL hash when isNav is enabled', () => {
151+
render(
152+
<Tabs activeKey={0} onSelect={() => undefined} isNav>
153+
<Tab eventKey={0} title={<TabTitleText>Tab item 1</TabTitleText>} href="#/items/1">
154+
Tab item 1
155+
</Tab>
156+
<Tab eventKey={1} title={<TabTitleText>Tab item 2</TabTitleText>} href="#/items/2">
157+
Tab item 2
158+
</Tab>
159+
</Tabs>
160+
);
161+
162+
expect(screen.getByRole('tab', { name: 'Tab item 2' })).toHaveAttribute('aria-selected', 'true');
163+
expect(screen.getByRole('tab', { name: 'Tab item 1' })).toHaveAttribute('aria-selected', 'false');
164+
});
165+
166+
test('should fall back to activeKey when no tab href matches the current hash', () => {
167+
window.location.hash = '#/items/unknown';
168+
169+
render(
170+
<Tabs activeKey={0} onSelect={() => undefined} component="nav">
171+
<Tab eventKey={0} title={<TabTitleText>Tab item 1</TabTitleText>} href="#/items/1">
172+
Tab item 1
173+
</Tab>
174+
<Tab eventKey={1} title={<TabTitleText>Tab item 2</TabTitleText>} href="#/items/2">
175+
Tab item 2
176+
</Tab>
177+
</Tabs>
178+
);
179+
180+
expect(screen.getByRole('tab', { name: 'Tab item 1' })).toHaveAttribute('aria-selected', 'true');
181+
expect(screen.getByRole('tab', { name: 'Tab item 2' })).toHaveAttribute('aria-selected', 'false');
182+
});
183+
184+
test('should fall back to activeKey when the URL hash is empty', () => {
185+
window.location.hash = '';
186+
187+
render(
188+
<Tabs activeKey={0} onSelect={() => undefined} component="nav">
189+
<Tab eventKey={0} title={<TabTitleText>Tab item 1</TabTitleText>} href="#/items/1">
190+
Tab item 1
191+
</Tab>
192+
<Tab eventKey={1} title={<TabTitleText>Tab item 2</TabTitleText>} href="#/items/2">
193+
Tab item 2
194+
</Tab>
195+
</Tabs>
196+
);
197+
198+
expect(screen.getByRole('tab', { name: 'Tab item 1' })).toHaveAttribute('aria-selected', 'true');
199+
expect(screen.getByRole('tab', { name: 'Tab item 2' })).toHaveAttribute('aria-selected', 'false');
200+
});
201+
150202
test('should respect later controlled selections after the initial hash match', async () => {
151203
const user = userEvent.setup();
152204
const ControlledTabs = () => {
@@ -241,6 +293,46 @@ describe('hash-based nav selection', () => {
241293
expect(screen.getByRole('tab', { name: 'Tab item 1' })).toHaveAttribute('aria-selected', 'true');
242294
expect(screen.getByRole('tab', { name: 'Tab item 2' })).toHaveAttribute('aria-selected', 'false');
243295
});
296+
297+
test('should prevent default anchor navigation when selecting a tab with an href', async () => {
298+
const user = userEvent.setup();
299+
window.location.hash = '';
300+
301+
render(
302+
<Tabs activeKey={0} onSelect={() => undefined}>
303+
<Tab eventKey={0} title={<TabTitleText>Tab item 1</TabTitleText>} href="#/items/1">
304+
Tab item 1
305+
</Tab>
306+
<Tab eventKey={1} title={<TabTitleText>Tab item 2</TabTitleText>} href="#/items/2">
307+
Tab item 2
308+
</Tab>
309+
</Tabs>
310+
);
311+
312+
await user.click(screen.getByRole('tab', { name: 'Tab item 2' }));
313+
314+
expect(window.location.hash).toBe('');
315+
});
316+
317+
test('should update the URL hash when selecting a nav tab with an href', async () => {
318+
const user = userEvent.setup();
319+
window.location.hash = '';
320+
321+
render(
322+
<Tabs activeKey={0} onSelect={() => undefined} isNav>
323+
<Tab eventKey={0} title={<TabTitleText>Users</TabTitleText>} href="#users">
324+
Users
325+
</Tab>
326+
<Tab eventKey={1} title={<TabTitleText>Containers</TabTitleText>} href="#containers">
327+
Containers
328+
</Tab>
329+
</Tabs>
330+
);
331+
332+
await user.click(screen.getByRole('tab', { name: 'Containers' }));
333+
334+
expect(window.location.hash).toBe('#containers');
335+
});
244336
});
245337

246338
test(`Renders with class ${styles.modifiers.initializingAccent} when uncontrolled expandable component initially mounts`, async () => {

packages/react-core/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exports[`should not render tablist aria-label or aria-labelledby when neither is
44
<DocumentFragment>
55
<div
66
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
7-
data-ouia-component-id="OUIA-Generated-Tabs-:r2n:"
7+
data-ouia-component-id="OUIA-Generated-Tabs-:r3b:"
88
data-ouia-component-type="PF6/Tabs"
99
data-ouia-safe="true"
1010
id="noTabListLabelTabs"
@@ -89,7 +89,7 @@ exports[`should render accessible tabs 1`] = `
8989
<nav
9090
aria-label="accessible Tabs example"
9191
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
92-
data-ouia-component-id="OUIA-Generated-Tabs-:r1b:"
92+
data-ouia-component-id="OUIA-Generated-Tabs-:r1v:"
9393
data-ouia-component-type="PF6/Tabs"
9494
data-ouia-safe="true"
9595
id="accessibleTabs"
@@ -206,7 +206,7 @@ exports[`should render box tabs 1`] = `
206206
<DocumentFragment>
207207
<div
208208
class="pf-v6-c-tabs pf-m-animate-current pf-m-box pf-m-initializing-accent"
209-
data-ouia-component-id="OUIA-Generated-Tabs-:r19:"
209+
data-ouia-component-id="OUIA-Generated-Tabs-:r1t:"
210210
data-ouia-component-type="PF6/Tabs"
211211
data-ouia-safe="true"
212212
id="boxTabs"
@@ -365,7 +365,7 @@ exports[`should render box tabs of secondary variant 1`] = `
365365
<DocumentFragment>
366366
<div
367367
class="pf-v6-c-tabs pf-m-animate-current pf-m-box pf-m-secondary pf-m-initializing-accent"
368-
data-ouia-component-id="OUIA-Generated-Tabs-:r1v:"
368+
data-ouia-component-id="OUIA-Generated-Tabs-:r2j:"
369369
data-ouia-component-type="PF6/Tabs"
370370
data-ouia-safe="true"
371371
id="boxSecondaryVariantTabs"
@@ -482,7 +482,7 @@ exports[`should render expandable vertical tabs 1`] = `
482482
<DocumentFragment>
483483
<div
484484
class="pf-v6-c-tabs pf-m-animate-current pf-m-vertical pf-m-expandable pf-m-initializing-accent"
485-
data-ouia-component-id="OUIA-Generated-Tabs-:r13:"
485+
data-ouia-component-id="OUIA-Generated-Tabs-:r1n:"
486486
data-ouia-component-type="PF6/Tabs"
487487
data-ouia-safe="true"
488488
id="verticalTabs"
@@ -497,7 +497,7 @@ exports[`should render expandable vertical tabs 1`] = `
497497
<button
498498
aria-labelledby="generated-id-text generated-id-button"
499499
class="pf-v6-c-button pf-m-plain"
500-
data-ouia-component-id="OUIA-Generated-Button-plain-:r14:"
500+
data-ouia-component-id="OUIA-Generated-Button-plain-:r1o:"
501501
data-ouia-component-type="PF6/Button"
502502
data-ouia-safe="true"
503503
id="generated-id-button"
@@ -689,7 +689,7 @@ exports[`should render filled tabs 1`] = `
689689
<DocumentFragment>
690690
<div
691691
class="pf-v6-c-tabs pf-m-animate-current pf-m-fill pf-m-initializing-accent"
692-
data-ouia-component-id="OUIA-Generated-Tabs-:r1d:"
692+
data-ouia-component-id="OUIA-Generated-Tabs-:r21:"
693693
data-ouia-component-type="PF6/Tabs"
694694
data-ouia-safe="true"
695695
id="filledTabs"
@@ -806,7 +806,7 @@ exports[`should render simple tabs 1`] = `
806806
<DocumentFragment>
807807
<div
808808
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
809-
data-ouia-component-id="OUIA-Generated-Tabs-:rn:"
809+
data-ouia-component-id="OUIA-Generated-Tabs-:r1b:"
810810
data-ouia-component-type="PF6/Tabs"
811811
data-ouia-safe="true"
812812
id="simpleTabs"
@@ -965,7 +965,7 @@ exports[`should render subtabs 1`] = `
965965
<DocumentFragment>
966966
<div
967967
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
968-
data-ouia-component-id="OUIA-Generated-Tabs-:r1f:"
968+
data-ouia-component-id="OUIA-Generated-Tabs-:r23:"
969969
data-ouia-component-type="PF6/Tabs"
970970
data-ouia-safe="true"
971971
id="primarieTabs"
@@ -1051,7 +1051,7 @@ exports[`should render subtabs 1`] = `
10511051
>
10521052
<div
10531053
class="pf-v6-c-tabs pf-m-animate-current pf-m-subtab pf-m-initializing-accent"
1054-
data-ouia-component-id="OUIA-Generated-Tabs-:r1h:"
1054+
data-ouia-component-id="OUIA-Generated-Tabs-:r25:"
10551055
data-ouia-component-type="PF6/Tabs"
10561056
data-ouia-safe="true"
10571057
id="subtabs"
@@ -1194,7 +1194,7 @@ exports[`should render tablist aria-label when provided 1`] = `
11941194
<DocumentFragment>
11951195
<div
11961196
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
1197-
data-ouia-component-id="OUIA-Generated-Tabs-:r2j:"
1197+
data-ouia-component-id="OUIA-Generated-Tabs-:r37:"
11981198
data-ouia-component-type="PF6/Tabs"
11991199
data-ouia-safe="true"
12001200
id="tabListLabelTabs"
@@ -1284,7 +1284,7 @@ exports[`should render tablist aria-labelledby when provided 1`] = `
12841284
</h2>
12851285
<div
12861286
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
1287-
data-ouia-component-id="OUIA-Generated-Tabs-:r2l:"
1287+
data-ouia-component-id="OUIA-Generated-Tabs-:r39:"
12881288
data-ouia-component-type="PF6/Tabs"
12891289
data-ouia-safe="true"
12901290
id="tabListLabelledByTabs"
@@ -1369,7 +1369,7 @@ exports[`should render tabs with eventKey Strings 1`] = `
13691369
<DocumentFragment>
13701370
<div
13711371
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
1372-
data-ouia-component-id="OUIA-Generated-Tabs-:r1j:"
1372+
data-ouia-component-id="OUIA-Generated-Tabs-:r27:"
13731373
data-ouia-component-type="PF6/Tabs"
13741374
data-ouia-safe="true"
13751375
id="eventKeyTabs"
@@ -1487,7 +1487,7 @@ exports[`should render tabs with no bottom border 1`] = `
14871487
<DocumentFragment>
14881488
<div
14891489
class="pf-v6-c-tabs pf-m-animate-current pf-m-no-border-bottom pf-m-initializing-accent"
1490-
data-ouia-component-id="OUIA-Generated-Tabs-:r21:"
1490+
data-ouia-component-id="OUIA-Generated-Tabs-:r2l:"
14911491
data-ouia-component-type="PF6/Tabs"
14921492
data-ouia-safe="true"
14931493
id="noBottomBorderTabs"
@@ -1604,7 +1604,7 @@ exports[`should render tabs with separate content 1`] = `
16041604
<DocumentFragment>
16051605
<div
16061606
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
1607-
data-ouia-component-id="OUIA-Generated-Tabs-:r1l:"
1607+
data-ouia-component-id="OUIA-Generated-Tabs-:r29:"
16081608
data-ouia-component-type="PF6/Tabs"
16091609
data-ouia-safe="true"
16101610
id="separateTabs"
@@ -1731,7 +1731,7 @@ exports[`should render uncontrolled tabs 1`] = `
17311731
<DocumentFragment>
17321732
<div
17331733
class="pf-v6-c-tabs pf-m-animate-current pf-m-initializing-accent"
1734-
data-ouia-component-id="OUIA-Generated-Tabs-:rv:"
1734+
data-ouia-component-id="OUIA-Generated-Tabs-:r1j:"
17351735
data-ouia-component-type="PF6/Tabs"
17361736
data-ouia-safe="true"
17371737
style="--pf-v6-c-tabs--link-accent--length: 0px; --pf-v6-c-tabs--link-accent--start: 0px;"
@@ -1889,7 +1889,7 @@ exports[`should render vertical tabs 1`] = `
18891889
<DocumentFragment>
18901890
<div
18911891
class="pf-v6-c-tabs pf-m-animate-current pf-m-vertical pf-m-initializing-accent"
1892-
data-ouia-component-id="OUIA-Generated-Tabs-:r11:"
1892+
data-ouia-component-id="OUIA-Generated-Tabs-:r1l:"
18931893
data-ouia-component-type="PF6/Tabs"
18941894
data-ouia-safe="true"
18951895
id="verticalTabs"

0 commit comments

Comments
 (0)