Skip to content

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

Open
tarunvashishth wants to merge 1 commit intopatternfly:mainfrom
tarunvashishth:tabs-should-work-with-url
Open

fix(tabs): honor hash-linked nav tabs#12303
tarunvashishth wants to merge 1 commit intopatternfly:mainfrom
tarunvashishth:tabs-should-work-with-url

Conversation

@tarunvashishth
Copy link
Copy Markdown

@tarunvashishth tarunvashishth commented Mar 28, 2026

Closes #12094

Summary by CodeRabbit

  • New Features
    • Navigation tabs now automatically select the active tab based on the URL hash, enabling users to bookmark and share direct links to specific tab views.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

Walkthrough

The change adds hash-based tab selection for nav-linked tabs. When isNav is true, the Tabs component now derives the active tab by matching the window location hash against each visible tab's href, enabling correct tab selection when opening a link with an anchor fragment.

Changes

Cohort / File(s) Summary
Hash-based Tab Selection
packages/react-core/src/components/Tabs/Tabs.tsx
Added DOM-dependent hash parsing helpers to derive the active tab by matching visible, non-disabled tab href hash segments against window.location.hash for nav tabs, with fallback to existing defaultActiveKey/activeKey logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 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 'fix(tabs): honor hash-linked nav tabs' clearly and concisely describes the main change: enabling tabs to honor URL hash fragments for navigation.
Linked Issues check ✅ Passed The code changes directly address issue #12094 by adding hash-based tab selection logic for nav tabs, allowing the component to select the correct tab when specified via URL fragment on page load.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing hash-based tab selection for nav tabs only, with no extraneous modifications or unrelated functionality added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@patternfly-build
Copy link
Copy Markdown
Collaborator

patternfly-build commented Mar 28, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 on lines +194 to 198
shownKeys:
this.props.defaultActiveKey !== undefined
? [hashActiveKey ?? this.props.defaultActiveKey]
: [hashActiveKey ?? this.props.activeKey], // only for mountOnEnter case
uncontrolledActiveKey: this.props.defaultActiveKey,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Initialize uncontrolledActiveKey with hash-derived key for consistency.

The shownKeys correctly prioritizes hashActiveKey, but uncontrolledActiveKey is still set only to defaultActiveKey. For the hash-based initial selection to persist after user interaction, uncontrolledActiveKey should also be seeded from the hash:

-      uncontrolledActiveKey: this.props.defaultActiveKey,
+      uncontrolledActiveKey: hashActiveKey ?? this.props.defaultActiveKey,

This change, combined with removing the render-time hash derivation, would ensure the hash is honored on initial load but doesn't override subsequent user selections.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
shownKeys:
this.props.defaultActiveKey !== undefined
? [hashActiveKey ?? this.props.defaultActiveKey]
: [hashActiveKey ?? this.props.activeKey], // only for mountOnEnter case
uncontrolledActiveKey: this.props.defaultActiveKey,
shownKeys:
this.props.defaultActiveKey !== undefined
? [hashActiveKey ?? this.props.defaultActiveKey]
: [hashActiveKey ?? this.props.activeKey], // only for mountOnEnter case
uncontrolledActiveKey: hashActiveKey ?? this.props.defaultActiveKey,
🤖 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 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).

Comment on lines +559 to +560
const hashActiveKey = getTabHashActiveKey({ children, component, isNav });
const localActiveKey = hashActiveKey ?? (defaultActiveKey !== undefined ? uncontrolledActiveKey : activeKey);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hash takes precedence on every render, potentially overriding user tab selections.

When hashActiveKey is recomputed on each render (line 559), it will continuously override uncontrolledActiveKey even after the user clicks a different tab. The URL hash doesn't automatically update when tabs are clicked via handleTabClick, so the hash-derived key will keep "winning" over the user's selection.

Consider computing the hash-based key only on initial mount (which you already do at line 186), and using it to seed the initial state rather than re-deriving it on every render:

-    const hashActiveKey = getTabHashActiveKey({ children, component, isNav });
-    const localActiveKey = hashActiveKey ?? (defaultActiveKey !== undefined ? uncontrolledActiveKey : activeKey);
+    const localActiveKey = defaultActiveKey !== undefined ? uncontrolledActiveKey : activeKey;

The initial shownKeys and uncontrolledActiveKey initialization in the constructor should handle the initial hash-based selection. Alternatively, you could update uncontrolledActiveKey in the constructor to use the hash:

-      uncontrolledActiveKey: this.props.defaultActiveKey,
+      uncontrolledActiveKey: hashActiveKey ?? this.props.defaultActiveKey,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hashActiveKey = getTabHashActiveKey({ children, component, isNav });
const localActiveKey = hashActiveKey ?? (defaultActiveKey !== undefined ? uncontrolledActiveKey : activeKey);
const localActiveKey = defaultActiveKey !== undefined ? uncontrolledActiveKey : activeKey;
🤖 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 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.

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

Labels

None yet

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

2 participants