Skip to content

fix(router): respect target attribute on inner component#5115

Merged
schiller-manuel merged 5 commits intoTanStack:mainfrom
leesb971204:fix/respect-target-inner-component
Sep 18, 2025
Merged

fix(router): respect target attribute on inner component#5115
schiller-manuel merged 5 commits intoTanStack:mainfrom
leesb971204:fix/respect-target-inner-component

Conversation

@leesb971204
Copy link
Contributor

@leesb971204 leesb971204 commented Sep 10, 2025

fixes #5011

Summary by CodeRabbit

  • Bug Fixes
    • Links now correctly respect the target attribute from rendered anchor elements when not explicitly provided as a prop. Clicks on links with target=_blank are no longer intercepted for client-side navigation, opening in a new tab as expected.
    • Explicit target props still override defaults from custom link components, ensuring predictable behavior.
    • Applies to both React Router and Solid Router link components, improving consistency with custom link implementations and browser defaults.

Signed-off-by: leesb971204 <leesb971204@gmail.com>
Signed-off-by: leesb971204 <leesb971204@gmail.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 10, 2025

Walkthrough

Updates modify Link click interception in React and Solid routers to compute an effective target from either the prop or the rendered anchor’s target attribute. Clicks with effective target not set or set to "_self" are intercepted; others (e.g., "_blank") follow browser default. Tests added to validate custom createLink components. No API changes.

Changes

Cohort / File(s) Summary
Link click interception behavior
packages/react-router/src/link.tsx, packages/solid-router/src/link.tsx
Compute effectiveTarget from prop or DOM anchor attribute; intercept clicks only when effectiveTarget is absent or equals _self. Prevents router interception for links with _blank set on the underlying anchor when no explicit prop is provided.
Tests for target handling with createLink
packages/react-router/tests/link.test.tsx, packages/solid-router/tests/link.test.tsx
Add tests verifying: (1) target set by custom link components (e.g., _blank) is respected and not intercepted; (2) explicit target prop overrides default from custom components, enabling internal navigation when set to _self.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant L as Link Component
  participant A as <a> Element
  participant R as Router
  participant B as Browser

  U->>L: Click
  L->>A: Read DOM target (elementTarget)
  L->>L: effectiveTarget = propTarget ?? elementTarget

  alt Intercept (effectiveTarget absent or "_self")
    L->>U: preventDefault()
    L->>R: navigate(to)
    R-->>L: navigation complete
  else Let browser handle (e.g., "_blank")
    L-->>U: no preventDefault
    L->>B: default navigation
    Note over B: Opens per target (e.g., new tab)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I nudge the links with twitching ear,
If target says “new tab,” I cheer!
When “_self” is set, I hop on through,
Router paths align true-blue.
Carrots clicked, assertions pass—
Tabs pop open, swift as grass.
Thump-thump! Merged at last. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly and accurately summarizes the main change — making the router respect an inner component's target attribute — and directly reflects the code edits and tests in the changeset.
Linked Issues Check ✅ Passed The patch implements the behavior requested in issue #5011 by deriving an effectiveTarget from the component prop or the DOM anchor and only intercepting clicks for _self or missing targets, and both react-router and solid-router tests were added to verify that inner-component target="_blank" prevents router interception while explicit target props override defaults.
Out of Scope Changes Check ✅ Passed All modifications are limited to Link implementations and their tests in react-router and solid-router, directly addressing target handling without changing public APIs or unrelated files according to the provided summary.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@nx-cloud
Copy link

nx-cloud bot commented Sep 10, 2025

View your CI Pipeline Execution ↗ for commit abf2149

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 3m View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 24s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-18 07:58:46 UTC

!disabled &&
!isCtrlEvent(e) &&
!e.defaultPrevented &&
(!target || target === '_self') &&
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the target prop is undefined, the condition treats it as _self, causing the router to intercept the click and prevent target="_blank" links from opening in a new tab.

Comment on lines +221 to +224
// Check actual element's target attribute as fallback
const elementTarget = (e.currentTarget as HTMLAnchorElement).target
const effectiveTarget = target !== undefined ? target : elementTarget

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If no target prop is provided, check the rendered element’s target attribute to ensure correct behavior.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 10, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@5115

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5115

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@5115

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@5115

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@5115

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@5115

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@5115

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@5115

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@5115

@tanstack/react-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-plugin@5115

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@5115

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@5115

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@5115

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@5115

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@5115

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@5115

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@5115

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@5115

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@5115

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@5115

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5115

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@5115

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@5115

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@5115

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@5115

@tanstack/solid-start-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-plugin@5115

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@5115

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@5115

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@5115

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@5115

@tanstack/start-server-functions-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-client@5115

@tanstack/start-server-functions-fetcher

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-fetcher@5115

@tanstack/start-server-functions-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-functions-server@5115

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@5115

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@5115

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@5115

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@5115

commit: abf2149

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
packages/react-router/src/link.tsx (1)

221-224: Make target handling robust (case-insensitive + generic Element) and don’t rely on anchor-only property.

Reading .target assumes an anchor and is case-sensitive for _self. Using getAttribute('target') and normalizing improves resilience (e.g., _SELF) and works with any element rendered via createLink.

Apply within this hunk:

-    // Check actual element's target attribute as fallback
-    const elementTarget = (e.currentTarget as HTMLAnchorElement).target
-    const effectiveTarget = target !== undefined ? target : elementTarget
+    // Check actual element's target attribute as fallback
+    const rawTarget =
+      (e.currentTarget as Element).getAttribute?.('target') ?? undefined
+    const effectiveTarget = target !== undefined ? target : rawTarget
+    const keywordTarget =
+      typeof effectiveTarget === 'string'
+        ? effectiveTarget.toLowerCase()
+        : undefined
@@
-      (!effectiveTarget || effectiveTarget === '_self') &&
+      (!keywordTarget || keywordTarget === '_self') &&

Additionally, to avoid clobbering a child component’s default target when the prop is undefined, consider returning target conditionally in the props spread later in this file:

// ...inside the return object
...(target !== undefined && { target }),

Also applies to: 229-229

packages/react-router/tests/link.test.tsx (1)

4686-4746: Remove unused window.open mock or assert on it.

The test doesn’t assert openMock calls, so the manual override adds noise and potential flakiness. Either drop it or use vi.spyOn(window, 'open') and assert.

Minimal cleanup within this hunk:

-    const originalOpen = window.open
-    const openMock = vi.fn()
-    window.open = openMock
@@
-    window.open = originalOpen

If you prefer to assert:

+    const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null as any)
@@
+    await waitFor(() => expect(openSpy).toHaveBeenCalled())
+    openSpy.mockRestore()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 834128e and 204fd8a.

📒 Files selected for processing (2)
  • packages/react-router/src/link.tsx (1 hunks)
  • packages/react-router/tests/link.test.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/react-router/tests/link.test.tsx (2)
packages/react-router/src/index.tsx (6)
  • createLink (151-151)
  • createRootRoute (262-262)
  • createRoute (259-259)
  • createRouter (277-277)
  • createMemoryHistory (121-121)
  • RouterProvider (287-287)
packages/react-router/src/router.ts (1)
  • createRouter (80-82)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview
🔇 Additional comments (1)
packages/react-router/tests/link.test.tsx (1)

4748-4796: LGTM: override semantics validated.

This cleanly proves target prop overrides a custom component’s default, and the router intercepts navigation when _self is effective.

@nlynzaad
Copy link
Contributor

nlynzaad commented Sep 17, 2025

other than making use of test-ids for the tests this looks good to me

Signed-off-by: leesb971204 <leesb971204@gmail.com>
@leesb971204
Copy link
Contributor Author

other than making use of test-ids for the tests this looks good to me

Thank you for the feedback.
I’ve updated it to perform the tests using data-testid.

@nlynzaad
Copy link
Contributor

one last thing please apply the same tests and update to solid-router

Signed-off-by: leesb971204 <leesb971204@gmail.com>
Signed-off-by: leesb971204 <leesb971204@gmail.com>
@leesb971204 leesb971204 changed the title fix(react-router): respect target attribute on inner component fix(router): respect target attribute on inner component Sep 18, 2025
@leesb971204
Copy link
Contributor Author

one last thing please apply the same tests and update to solid-router

I’ve added the related logic and tests to solid-router.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
packages/solid-router/src/link.tsx (1)

419-419: Style gating likely never applies

Object.keys is called on the accessor function, not its value, so the style prop is never added. Prefer checking keys of resolvedStyle() while still passing the accessor.

Apply this diff:

-        ...(Object.keys(resolvedStyle).length && { style: resolvedStyle }),
+        ...(Object.keys(resolvedStyle()).length && { style: resolvedStyle }),
packages/solid-router/tests/link.test.tsx (1)

4249-4307: Trim unused window.open stub

The test doesn’t assert on window.open; the stub can be removed to reduce globals churn.

-    const originalOpen = window.open
-    const openMock = vi.fn()
-    window.open = openMock
...
-    window.open = originalOpen
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 39018e1 and abf2149.

📒 Files selected for processing (2)
  • packages/solid-router/src/link.tsx (1 hunks)
  • packages/solid-router/tests/link.test.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (2)
packages/solid-router/src/link.tsx (1)

267-271: Target fallback logic LGTM

Deriving effectiveTarget from either prop or element attribute and gating interception on it fixes the custom link/new‑tab case.

Also applies to: 276-276

packages/solid-router/tests/link.test.tsx (1)

4310-4360: Override behavior test LGTM

Confirms that an explicit target prop overrides a component’s default.

@schiller-manuel schiller-manuel merged commit 2c8d5ce into TanStack:main Sep 18, 2025
6 checks passed
naoya7076 pushed a commit to naoya7076/router that referenced this pull request Feb 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

target is not respected on link components created with createLink

3 participants