-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix: serial head execution #6093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -683,8 +683,6 @@ const runLoader = async ( | |
| // so we need to wait for it to resolve before | ||
| // we can use the options | ||
| if (route._lazyPromise) await route._lazyPromise | ||
| const headResult = executeHead(inner, matchId, route) | ||
| const head = headResult ? await headResult : undefined | ||
| const pendingPromise = match._nonReactive.minPendingPromise | ||
| if (pendingPromise) await pendingPromise | ||
|
|
||
|
|
@@ -697,7 +695,6 @@ const runLoader = async ( | |
| status: 'success', | ||
| isFetching: false, | ||
| updatedAt: Date.now(), | ||
| ...head, | ||
| })) | ||
| } catch (e) { | ||
| let error = e | ||
|
|
@@ -721,28 +718,17 @@ const runLoader = async ( | |
| onErrorError, | ||
| ) | ||
| } | ||
| const headResult = executeHead(inner, matchId, route) | ||
| const head = headResult ? await headResult : undefined | ||
| inner.updateMatch(matchId, (prev) => ({ | ||
| ...prev, | ||
| error, | ||
| status: 'error', | ||
| isFetching: false, | ||
| ...head, | ||
| })) | ||
| } | ||
| } catch (err) { | ||
| const match = inner.router.getMatch(matchId) | ||
| // in case of a redirecting match during preload, the match does not exist | ||
| if (match) { | ||
| const headResult = executeHead(inner, matchId, route) | ||
| if (headResult) { | ||
| const head = await headResult | ||
| inner.updateMatch(matchId, (prev) => ({ | ||
| ...prev, | ||
| ...head, | ||
| })) | ||
| } | ||
| match._nonReactive.loaderPromise = undefined | ||
| } | ||
| handleRedirectAndNotFound(inner, match, err) | ||
|
|
@@ -767,14 +753,6 @@ const loadRouteMatch = async ( | |
|
|
||
| if (shouldSkipLoader(inner, matchId)) { | ||
| if (inner.router.isServer) { | ||
| const headResult = executeHead(inner, matchId, route) | ||
| if (headResult) { | ||
| const head = await headResult | ||
| inner.updateMatch(matchId, (prev) => ({ | ||
| ...prev, | ||
| ...head, | ||
| })) | ||
| } | ||
| return inner.router.getMatch(matchId)! | ||
| } | ||
| } else { | ||
|
|
@@ -852,18 +830,6 @@ const loadRouteMatch = async ( | |
| })() | ||
| } else if (status !== 'success' || (loaderShouldRunAsync && inner.sync)) { | ||
| await runLoader(inner, matchId, index, route) | ||
| } else { | ||
| // if the loader did not run, still update head. | ||
| // reason: parent's beforeLoad may have changed the route context | ||
| // and only now do we know the route context (and that the loader would not run) | ||
| const headResult = executeHead(inner, matchId, route) | ||
| if (headResult) { | ||
| const head = await headResult | ||
| inner.updateMatch(matchId, (prev) => ({ | ||
| ...prev, | ||
| ...head, | ||
| })) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -931,7 +897,51 @@ export async function loadMatches(arg: { | |
| for (let i = 0; i < max; i++) { | ||
| inner.matchPromises.push(loadRouteMatch(inner, i)) | ||
| } | ||
| await Promise.all(inner.matchPromises) | ||
| // Use allSettled to ensure all loaders complete regardless of success/failure | ||
| const results = await Promise.allSettled(inner.matchPromises) | ||
|
|
||
| const failures = results | ||
| // TODO when we drop support for TS 5.4, we can use the built-in type guard for PromiseRejectedResult | ||
| .filter( | ||
| (result): result is PromiseRejectedResult => | ||
| result.status === 'rejected', | ||
| ) | ||
| .map((result) => result.reason) | ||
|
|
||
| // Find the first redirect error and throw it immediately (skip head execution) | ||
| for (const err of failures) { | ||
| if (isRedirect(err)) { | ||
| throw err | ||
| } | ||
| } | ||
|
|
||
| // serially execute head functions after all loaders have completed (successfully or not) | ||
| // Each head execution is wrapped in try-catch to ensure all heads run even if one fails | ||
| // TODO: should we break out of head execution on first failure? | ||
| for (const match of inner.matches) { | ||
| const { id: matchId, routeId } = match | ||
| const route = inner.router.looseRoutesById[routeId]! | ||
| try { | ||
| const headResult = executeHead(inner, matchId, route) | ||
| if (headResult) { | ||
| const head = await headResult | ||
| inner.updateMatch(matchId, (prev) => ({ | ||
| ...prev, | ||
| ...head, | ||
| })) | ||
| } | ||
| } catch (err) { | ||
| // Log error but continue executing other head functions | ||
| console.error(`Error executing head for route ${routeId}:`, err) | ||
| } | ||
| } | ||
|
|
||
| // After head execution, check for notFound errors and throw the first one | ||
| for (const err of failures) { | ||
| if (isNotFound(err)) { | ||
| throw err | ||
| } | ||
| } | ||
|
Comment on lines
+939
to
+944
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find it a little curious that we call If we have |
||
|
|
||
| const readyPromise = triggerOnReady(inner) | ||
| if (isPromise(readyPromise)) await readyPromise | ||
|
|
@@ -945,7 +955,6 @@ export async function loadMatches(arg: { | |
| throw err | ||
| } | ||
| } | ||
|
|
||
| return inner.matches | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: TanStack/router
Length of output: 1108
🏁 Script executed:
Repository: TanStack/router
Length of output: 368
🏁 Script executed:
Repository: TanStack/router
Length of output: 189
🏁 Script executed:
Repository: TanStack/router
Length of output: 1983
🏁 Script executed:
Repository: TanStack/router
Length of output: 417
🏁 Script executed:
Repository: TanStack/router
Length of output: 116
🏁 Script executed:
Repository: TanStack/router
Length of output: 1496
🏁 Script executed:
Repository: TanStack/router
Length of output: 732
🏁 Script executed:
Repository: TanStack/router
Length of output: 2084
🏁 Script executed:
Repository: TanStack/router
Length of output: 1193
Remove 'head' from directVisitTestMatrix to match navigationTestMatrix
The
via-head.tsxroute file does not exist in the not-found routes directory. ThenavigationTestMatrix(line 29) correctly excludes 'head' from the thrower options, butdirectVisitTestMatrix(line 61) still includes it. This will cause the direct visit test to fail when attempting to navigate to the non-existent/not-found/via-headroute. Either restore thevia-head.tsxroute file or remove 'head' fromdirectVisitTestMatrixto align with the navigation test configuration.🤖 Prompt for AI Agents