Skip to content

fix(tabs): honor hash-linked nav tabs - #12303

Open
tarunvashishth wants to merge 3 commits into
patternfly:mainfrom
tarunvashishth:tabs-should-work-with-url
Open

fix(tabs): honor hash-linked nav tabs#12303
tarunvashishth wants to merge 3 commits into
patternfly:mainfrom
tarunvashishth:tabs-should-work-with-url

Conversation

@tarunvashishth

@tarunvashishth tarunvashishth commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Closes #12094

Summary by CodeRabbit

  • New Features
    • Navigation-style tabs (nav/isNav) can now auto-select from the current URL hash (#...). Hidden, disabled, and aria-disabled tabs are skipped. If there’s no hash match, selection falls back to the configured active tab. Clicking a nav tab updates the URL hash accordingly (without full page navigation).
  • Tests
    • Added automated coverage for hash-based selection, fallback behavior, skipping hidden/disabled tabs, and verifying link/hash behavior for both controlled and uncontrolled tabs.

@coderabbitai

coderabbitai Bot commented Mar 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b3a3158-de21-4679-8609-dd737604e9d0

📥 Commits

Reviewing files that changed from the base of the PR and between 1a2ef16 and 385e7ba.

⛔ Files ignored due to path filters (1)
  • packages/react-core/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (2)
  • packages/react-core/src/components/Tabs/Tabs.tsx
  • packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-core/src/components/Tabs/tests/Tabs.test.tsx
  • packages/react-core/src/components/Tabs/Tabs.tsx

Walkthrough

Nav-style Tabs now derive initial selection from the URL hash, skip hidden or disabled tabs, synchronize hash updates on selection, and preserve controlled, uncontrolled, and non-nav behavior.

Changes

Hash-based Tabs navigation

Layer / File(s) Summary
Hash selection and tab state flow
packages/react-core/src/components/Tabs/Tabs.tsx
Matches enabled tab href fragments against window.location.hash, prioritizes the match during initialization and rendering, and updates selection and URL state on clicks.
Hash navigation validation
packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx
Covers matching and fallback behavior, controlled and uncontrolled updates, excluded tabs, anchor handling, URL updates, and non-nav Tabs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser as window.location
  participant Tabs
  participant TabAnchor as tab anchor
  Browser->>Tabs: provide location.hash
  Tabs->>Tabs: match enabled href fragment
  Tabs-->>Tabs: render matching active tab
  TabAnchor->>Tabs: click selected tab
  Tabs->>Browser: update URL hash
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main change: Tabs now honor hash-linked nav tabs.
Linked Issues check ✅ Passed The changes honor URL fragments for nav tabs on load and on interaction, matching the issue's expected selection behavior.
Out of Scope Changes check ✅ Passed All code changes stay within Tabs hash-linked nav behavior and its tests, with no clear unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch tabs-should-work-with-url

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@patternfly-build

patternfly-build commented Mar 28, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-core/src/components/Tabs/Tabs.tsx`:
- Around line 194-198: Seed the uncontrolledActiveKey state using the
hash-derived key so initial selection matches shownKeys: set
uncontrolledActiveKey to hashActiveKey ?? this.props.defaultActiveKey (mirroring
shownKeys logic that uses hashActiveKey ?? ...). Update the initializer that
defines uncontrolledActiveKey in the Tabs component so it references
hashActiveKey, and ensure any later render-time hash derivation logic is removed
or not used to override user interactions after mount (preserve user changes to
uncontrolledActiveKey afterwards).
- Around line 145-156: Tests are missing for getTabHashActiveKey to verify
hash-based selection; add unit tests in the Tabs spec that set
window.location.hash (use a beforeEach to assign a hash and afterEach to clear
it) and assert getTabHashActiveKey (via rendering Tabs or importing the function
if exported) returns the expected eventKey when a child TabElement has matching
href, and does not return keys for hidden/disabled tabs or when component/isNav
conditions are unmet; target the getTabHashActiveKey logic and exercise
Tabs/TabsProps behavior with TabElement props (href, isHidden, isDisabled,
isAriaDisabled) so hash-based navigation is covered.
- Around line 559-560: The code currently recalculates hashActiveKey each render
(getTabHashActiveKey) and then uses it to set localActiveKey, which causes the
URL hash to override user clicks; instead, remove the per-render usage of
getTabHashActiveKey and seed uncontrolledActiveKey from the hash only on initial
mount/constructor (where you already compute it around line ~186), or use the
existing initial shownKeys/uncontrolledActiveKey initialization to incorporate
the hash-derived key; update Tabs constructor/initialization to set
uncontrolledActiveKey from getTabHashActiveKey once and ensure handleTabClick
updates uncontrolledActiveKey (not the URL hash) so subsequent renders respect
the user's selection rather than recomputing hashActiveKey each render.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3544653a-d87d-4a92-8b53-8011dd08fcc6

📥 Commits

Reviewing files that changed from the base of the PR and between e8f083e and 7e3c952.

📒 Files selected for processing (1)
  • packages/react-core/src/components/Tabs/Tabs.tsx

Comment on lines +145 to +156
const getTabHashActiveKey = ({ children, component, isNav }: Pick<TabsProps, 'children' | 'component' | 'isNav'>) => {
if (!canUseDOM || !(component === TabsComponent.nav || isNav) || !window.location.hash) {
return undefined;
}

return Children.toArray(children)
.filter((child): child is TabElement => isValidElement(child))
.filter(({ props }) => !props.isHidden)
.find(
({ props }) => !props.isDisabled && !props.isAriaDisabled && getHashFromHref(props.href) === window.location.hash
)?.props.eventKey;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Tabs tests have any hash-related setup
rg -n "location.hash|window.location" packages/react-core/src/components/Tabs/__tests__/

Repository: patternfly/patternfly-react

Length of output: 53


Add test coverage for the getTabHashActiveKey function with hash-based navigation.

The new getTabHashActiveKey function accesses window.location.hash and should be tested to ensure the hash-based tab selection works correctly and prevent regressions. Currently, no hash-related test setup exists in the Tabs tests, while the Nav component tests demonstrate the pattern with window.location.hash setup in a beforeEach block.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-core/src/components/Tabs/Tabs.tsx` around lines 145 - 156,
Tests are missing for getTabHashActiveKey to verify hash-based selection; add
unit tests in the Tabs spec that set window.location.hash (use a beforeEach to
assign a hash and afterEach to clear it) and assert getTabHashActiveKey (via
rendering Tabs or importing the function if exported) returns the expected
eventKey when a child TabElement has matching href, and does not return keys for
hidden/disabled tabs or when component/isNav conditions are unmet; target the
getTabHashActiveKey logic and exercise Tabs/TabsProps behavior with TabElement
props (href, isHidden, isDisabled, isAriaDisabled) so hash-based navigation is
covered.

Comment thread packages/react-core/src/components/Tabs/Tabs.tsx Outdated
Comment thread packages/react-core/src/components/Tabs/Tabs.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx (1)

122-129: Restore previous hash instead of forcing empty cleanup.

Line 128 always sets window.location.hash = ''. Prefer restoring the pre-test value for better isolation if surrounding tests ever rely on a non-empty hash.

♻️ Suggested tweak
 describe('hash-based nav selection', () => {
+  let previousHash = '';
+
   beforeEach(() => {
+    previousHash = window.location.hash;
     window.location.hash = '#/items/2';
   });

   afterEach(() => {
-    window.location.hash = '';
+    window.location.hash = previousHash;
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx` around lines
122 - 129, The afterEach cleanup currently forces window.location.hash = ''
which can break other tests; capture the original hash in the beforeEach (e.g.,
store it in a local variable like originalHash) and in afterEach restore
window.location.hash = originalHash so the test suite returns the location state
to what it was; update the beforeEach/afterEach surrounding the
describe('hash-based nav selection') block to use that stored value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx`:
- Around line 122-129: The afterEach cleanup currently forces
window.location.hash = '' which can break other tests; capture the original hash
in the beforeEach (e.g., store it in a local variable like originalHash) and in
afterEach restore window.location.hash = originalHash so the test suite returns
the location state to what it was; update the beforeEach/afterEach surrounding
the describe('hash-based nav selection') block to use that stored value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2c4b6d9f-e34c-4693-9b87-f4036d496206

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3c952 and 9122887.

📒 Files selected for processing (2)
  • packages/react-core/src/components/Tabs/Tabs.tsx
  • packages/react-core/src/components/Tabs/__tests__/Tabs.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-core/src/components/Tabs/Tabs.tsx

@github-actions

Copy link
Copy Markdown

This PR has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.

@github-actions github-actions Bot added the Stale label May 28, 2026
@rebeccaalpert
rebeccaalpert self-requested a review July 22, 2026 17:32

@rebeccaalpert rebeccaalpert left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed a re-rendering bug when tabs were clicked, and that when isNav was removed from the nav tabs example, it stopped working. Aside from that, it looks fine and better than the nav tabs example on PatternFly.org.

I submitted an extra commit with CodeRabbit-suggested tests and an update for the re-rendering issue that will require review from someone else.

@rebeccaalpert
rebeccaalpert force-pushed the tabs-should-work-with-url branch from 1a2ef16 to 385e7ba Compare July 28, 2026 19:02
tarunvashishth and others added 3 commits July 28, 2026 15:07
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
@rebeccaalpert
rebeccaalpert force-pushed the tabs-should-work-with-url branch from 385e7ba to ca039ec Compare July 28, 2026 19:14
@rebeccaalpert

Copy link
Copy Markdown
Member

Rebased.

@kmcfaul kmcfaul left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavior looks good to me locally, just a small non-blocking suggestion for un-nesting a branching if statement.

shownKeys: this.props.defaultActiveKey !== undefined ? [this.props.defaultActiveKey] : [this.props.activeKey], // only for mountOnEnter case
uncontrolledActiveKey: this.props.defaultActiveKey,
shownKeys:
this.props.defaultActiveKey !== undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is a little nested, hashActiveKey ?? this.props.defaultActiveKey ?? this.props.activeKey should mean the same thing and read a little clearer I think.

Not a blocker overall.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug - Tabs - Tabs linked to nav elements does not highlight url from anchor

4 participants