Skip to content

Conversation

@leesb971204
Copy link
Contributor

@leesb971204 leesb971204 commented Jan 20, 2026

fixes #6388

Summary

  • Fixed a bug where rapid navigation between parameterized routes caused errorComponent to render incorrectly

Problem

When rapidly navigating between parameterized routes (e.g., /something/1 → /something/2 → /something/1), the errorComponent was incorrectly triggered with a TypeError.

Root cause: The existing AbortError handler set status: 'success' without setting loaderData. When the component tried to access loaderData, it was undefined, causing a TypeError that was caught by the error boundary.

Solution

Distinguish between two types of AbortError by checking abortController.signal.aborted:

AbortError Type signal.aborted Handling
Intentional (loader throws AbortError directly) false Original behavior (set status to 'success')
Navigation abort (cancelMatches triggered) true Don't update match state, only resolve loaderPromise

Additionally, after awaiting a previous loaderPromise, check if the match status is still 'pending'. If so, the previous loader was aborted and a fresh loader needs to run.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed rapid navigation between parameterized routes so aborted loads no longer trigger error UI and final route renders reliably.
  • Tests

    • Added an end-to-end test reproducer validating rapid param-route navigation does not show error components.
    • Updated existing navigation store-update tests to reflect corrected update counts during navigation.

✏️ Tip: You can customize this high-level summary in your review settings.

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

coderabbitai bot commented Jan 20, 2026

📝 Walkthrough

Walkthrough

Refines loader abort/error handling to distinguish navigation-caused aborts and avoid spurious errorComponent renders; adds an end-to-end test reproducer that rapidly navigates parameterized routes to validate the fix.

Changes

Cohort / File(s) Summary
E2E test reproducer
packages/react-router/tests/loaders.test.tsx
Adds an end-to-end test that rapidly navigates between parameterized routes, using an AbortController-managed loader and an errorComponent spy to assert no erroneous error renders and that the latest loader completes.
Loader abort & post-wait logic
packages/router-core/src/load-matches.ts
Adjusts AbortError handling to detect navigation-caused aborts (treating them differently from other aborts), clears/resolves loaderPromise appropriately, and uses the awaited match state (matchAfterWait) with an early return if it's no longer pending to prevent stale continuation.
Minor test expectation updates
packages/react-router/tests/store-updates-during-navigation.test.tsx
Increases expected store update counts from 7 → 8 in two tests to reflect adjusted update behavior during navigation.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client
participant RouterCore as Router-Core
participant Loader as Loader (with AbortController)
participant MatchStore as Match State

Client->>RouterCore: navigate to /something/1
RouterCore->>Loader: start loader (attach AbortController)
Loader-->>RouterCore: pending result / can be aborted
Client->>RouterCore: rapid navigate to /something/2
RouterCore->>Loader: abort previous loader (navigation-caused)
RouterCore->>MatchStore: mark previous match as aborted/clear loaderPromise
RouterCore->>Loader: start new loader for /something/2
Loader-->>RouterCore: resolves successfully
RouterCore->>MatchStore: commit success for latest match (no errorComponent render)
Client->>RouterCore: navigate back to /something/1
RouterCore->>Loader: start loader for /something/1
Loader-->>RouterCore: resolves successfully
RouterCore->>MatchStore: commit success for latest match (final render)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰
Quick hops between params, I prance,
Aborts now know to yield their chance.
Pending stays until the right one lands,
No false alarms across the lands —
Hooray, smooth routes and steady hands! ✨

🚥 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 accurately describes the main change: fixing AbortError handling logic during rapid navigation, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR addresses all coding requirements from issue #6388: distinguishes abort cases, prevents errorComponent rendering, ensures proper match status during rapid navigation, and validates with a comprehensive reproducer test.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the rapid navigation abort handling issue. The test updates reflect the expected behavior changes from the core logic fixes.
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 docstrings

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

@leesb971204 leesb971204 changed the title fix(router-core): fix handle AbortError logic fix(router-core): fix handle AbortError logic when rapid navigation Jan 20, 2026
@nx-cloud
Copy link

nx-cloud bot commented Jan 20, 2026

View your CI Pipeline Execution ↗ for commit b08ca1e

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

☁️ Nx Cloud last updated this comment at 2026-01-20 04:37:21 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 20, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6426

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6426

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6426

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: b08ca1e

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: 1

🤖 Fix all issues with AI agents
In `@packages/router-core/src/load-matches.ts`:
- Around line 783-792: In loadRouteMatch ensure any pending timeout set during
this load cycle is cleared before the early return: if you created a
pendingTimeout (e.g., pendingTimeout or inner.pendingTimeout) clear it with
clearTimeout(...) and reset the variable (e.g., pendingTimeout = undefined or
inner.pendingTimeout = undefined) immediately before returning when
matchAfterWait.status !== 'pending'; do this near the block that reads
matchAfterWait and calls handleRedirectAndNotFound so pending timers don't
persist and block future pending scheduling.
🧹 Nitpick comments (1)
packages/react-router/tests/loaders.test.tsx (1)

828-936: Tighten final-state assertions to avoid false positives.

Right now the test passes as long as some param page renders and the loader completes. Consider asserting the final param value and expected loader completion id to make the repro more specific (Line 933-936).

♻️ Suggested test assertion enhancement
   const paramPage = await screen.findByTestId('param-page')
   expect(paramPage).toBeInTheDocument()
-  expect(loaderCompleteMock).toHaveBeenCalled()
+  expect(paramPage).toHaveTextContent('Param Component 1')
+  expect(loaderCompleteMock).toHaveBeenCalledWith('1')

Comment on lines +783 to +792
const matchAfterWait = inner.router.getMatch(matchId)!
const error = matchAfterWait._nonReactive.error || matchAfterWait.error
if (error) {
handleRedirectAndNotFound(inner, match, error)
handleRedirectAndNotFound(inner, matchAfterWait, error)
}
} else {

if (matchAfterWait.status !== 'pending') {
return matchAfterWait
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clear pendingTimeout before the early return.

The new early return skips the cleanup block at the end of loadRouteMatch. If a pending timeout was set in this load cycle, it can remain attached and block future pending scheduling. Consider clearing it before returning (Line 789).

🧹 Suggested cleanup before return
       if (matchAfterWait.status !== 'pending') {
+        clearTimeout(matchAfterWait._nonReactive.pendingTimeout)
+        matchAfterWait._nonReactive.pendingTimeout = undefined
         return matchAfterWait
       }
📝 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 matchAfterWait = inner.router.getMatch(matchId)!
const error = matchAfterWait._nonReactive.error || matchAfterWait.error
if (error) {
handleRedirectAndNotFound(inner, match, error)
handleRedirectAndNotFound(inner, matchAfterWait, error)
}
} else {
if (matchAfterWait.status !== 'pending') {
return matchAfterWait
}
}
const matchAfterWait = inner.router.getMatch(matchId)!
const error = matchAfterWait._nonReactive.error || matchAfterWait.error
if (error) {
handleRedirectAndNotFound(inner, matchAfterWait, error)
}
if (matchAfterWait.status !== 'pending') {
clearTimeout(matchAfterWait._nonReactive.pendingTimeout)
matchAfterWait._nonReactive.pendingTimeout = undefined
return matchAfterWait
}
}
🤖 Prompt for AI Agents
In `@packages/router-core/src/load-matches.ts` around lines 783 - 792, In
loadRouteMatch ensure any pending timeout set during this load cycle is cleared
before the early return: if you created a pendingTimeout (e.g., pendingTimeout
or inner.pendingTimeout) clear it with clearTimeout(...) and reset the variable
(e.g., pendingTimeout = undefined or inner.pendingTimeout = undefined)
immediately before returning when matchAfterWait.status !== 'pending'; do this
near the block that reads matchAfterWait and calls handleRedirectAndNotFound so
pending timers don't persist and block future pending scheduling.

Signed-off-by: leesb971204 <leesb971204@gmail.com>
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.

Aborted loader request renders errorComponent and on rapid param navigation with undefined error

1 participant