fix: preserve suspense promises for pending route matches#7051
fix: preserve suspense promises for pending route matches#7051
Conversation
Keep the current suspense promise attached while a route still renders as pending. This prevents aborted or invalidated reloads from throwing undefined instead of suspending.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
💤 Files with no reviewable changes (1)
📝 WalkthroughWalkthroughPreserves per-match pending render promises and avoids clearing Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant RouterCore
participant Match
participant Loader
Client->>RouterCore: navigate to /first
RouterCore->>Match: start loader (creates loaderPromise)
Match->>Loader: run loader (holds until resolved/aborted)
Note right of Match: create pendingRenderPromise\nand throw it to suspend render
Client->>RouterCore: navigate to /second
RouterCore->>Match: abort /first loader (reject loaderPromise)
RouterCore->>Match: do NOT clear loadPromise for pending match
RouterCore->>Loader: resolve /second loader
RouterCore->>Client: commit /second render with loader data
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
View your CI Pipeline Execution ↗ for commit 8e16dff
☁️ Nx Cloud last updated this comment at |
🚀 Changeset Version Preview2 package(s) bumped directly, 20 bumped as dependents. 🟩 Patch bumps
|
Bundle Size Benchmarks
Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better. |
Keep resolving shared load promises in core so Solid suspense and selective SSR can complete, but let React create a temporary local suspense promise when a pending match no longer has a pending load promise to throw.
This reverts commit 00ca45c.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/react-router/src/Match.tsx (1)
488-511: Unreachable server-side code in client-only component.Since
MatchInnerClientis only called when!(isServer ?? router.isServer)(see line 263), theisServercheck at line 494 will always be false. The server-side error rendering code (lines 495-507) is unreachable.♻️ Optional: Remove dead code
if (match.status === 'error') { - // If we're on the server, we need to use React's new and super - // wonky api for throwing errors from a server side render inside - // of a suspense boundary. This is the only way to get - // renderToPipeableStream to not hang indefinitely. - // We'll serialize the error and rethrow it on the client. - if (isServer ?? router.isServer) { - const RouteErrorComponent = - (route.options.errorComponent ?? - router.options.defaultErrorComponent) || - ErrorComponent - return ( - <RouteErrorComponent - error={match.error as any} - reset={undefined as any} - info={{ - componentStack: '', - }} - /> - ) - } - throw match.error }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/react-router/src/Match.tsx` around lines 488 - 511, The server-side rendering branch inside the match.status === 'error' block is unreachable in MatchInnerClient because MatchInnerClient is only invoked when !(isServer ?? router.isServer); remove the dead server-specific code (the isServer ?? router.isServer check and the RouteErrorComponent JSX return) and leave the client behavior (throw match.error) or replace it with the appropriate client-only error handling; update the match.status === 'error' block in MatchInnerClient (referencing match, isServer, router.isServer, and RouteErrorComponent) to eliminate the unreachable server render path.
🤖 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-router/src/Match.tsx`:
- Around line 488-511: The server-side rendering branch inside the match.status
=== 'error' block is unreachable in MatchInnerClient because MatchInnerClient is
only invoked when !(isServer ?? router.isServer); remove the dead
server-specific code (the isServer ?? router.isServer check and the
RouteErrorComponent JSX return) and leave the client behavior (throw
match.error) or replace it with the appropriate client-only error handling;
update the match.status === 'error' block in MatchInnerClient (referencing
match, isServer, router.isServer, and RouteErrorComponent) to eliminate the
unreachable server render path.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 62dea7b3-5421-4bb4-adc3-2d8f241654e7
📒 Files selected for processing (2)
packages/react-router/src/Match.tsxpackages/router-core/src/load-matches.ts
💤 Files with no reviewable changes (1)
- packages/router-core/src/load-matches.ts
Summary
pendinginvalidate({ forcePending: true })regression test that keeps rendering the suspense fallback instead of throwingundefinedTesting
CI=1 NX_DAEMON=false pnpm nx run @tanstack/react-router:test:unit --outputStyle=stream --skipRemoteCacheCI=1 NX_DAEMON=false pnpm nx run @tanstack/react-router:test:types --outputStyle=stream --skipRemoteCacheCI=1 NX_DAEMON=false pnpm nx run @tanstack/react-router:test:eslint --outputStyle=stream --skipRemoteCacheSummary by CodeRabbit
Bug Fixes
Tests