@tanstack/router-core 1.134.18 → 1.135.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Matches.ts CHANGED
@@ -148,6 +148,8 @@ export interface RouteMatch<
148
148
  displayPendingPromise?: Promise<void>
149
149
  minPendingPromise?: ControlledPromise<void>
150
150
  dehydrated?: boolean
151
+ /** @internal */
152
+ error?: unknown
151
153
  }
152
154
  loaderData?: TLoaderData
153
155
  /** @internal */
@@ -111,6 +111,8 @@ const handleRedirectAndNotFound = (
111
111
 
112
112
  const status = isRedirect(err) ? 'redirected' : 'notFound'
113
113
 
114
+ match._nonReactive.error = err
115
+
114
116
  inner.updateMatch(match.id, (prev) => ({
115
117
  ...prev,
116
118
  status,
@@ -211,7 +213,7 @@ const isBeforeLoadSsr = (
211
213
 
212
214
  // in SPA mode, only SSR the root route
213
215
  if (inner.router.isShell()) {
214
- existingMatch.ssr = matchId === rootRouteId
216
+ existingMatch.ssr = route.id === rootRouteId
215
217
  return
216
218
  }
217
219
 
@@ -560,9 +562,23 @@ const getLoaderContext = (
560
562
  route: AnyRoute,
561
563
  ): LoaderFnContext => {
562
564
  const parentMatchPromise = inner.matchPromises[index - 1] as any
563
- const { params, loaderDeps, abortController, context, cause } =
565
+ const { params, loaderDeps, abortController, cause } =
564
566
  inner.router.getMatch(matchId)!
565
567
 
568
+ let context = inner.router.options.context ?? {}
569
+
570
+ for (let i = 0; i <= index; i++) {
571
+ const innerMatch = inner.matches[i]
572
+ if (!innerMatch) continue
573
+ const m = inner.router.getMatch(innerMatch.id)
574
+ if (!m) continue
575
+ context = {
576
+ ...context,
577
+ ...(m.__routeContext ?? {}),
578
+ ...(m.__beforeLoadContext ?? {}),
579
+ }
580
+ }
581
+
566
582
  const preload = resolvePreload(inner, matchId)
567
583
 
568
584
  return {
@@ -750,8 +766,9 @@ const loadRouteMatch = async (
750
766
  }
751
767
  await prevMatch._nonReactive.loaderPromise
752
768
  const match = inner.router.getMatch(matchId)!
753
- if (match.error) {
754
- handleRedirectAndNotFound(inner, match, match.error)
769
+ const error = match._nonReactive.error || match.error
770
+ if (error) {
771
+ handleRedirectAndNotFound(inner, match, error)
755
772
  }
756
773
  } else {
757
774
  // This is where all of the stale-while-revalidate magic happens
package/src/path.ts CHANGED
@@ -377,7 +377,6 @@ function baseParsePathname(pathname: string): ReadonlyArray<Segment> {
377
377
  interface InterpolatePathOptions {
378
378
  path?: string
379
379
  params: Record<string, unknown>
380
- leaveWildcards?: boolean
381
380
  leaveParams?: boolean
382
381
  // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params
383
382
  decodeCharMap?: Map<string, string>
@@ -403,7 +402,6 @@ type InterPolatePathResult = {
403
402
  export function interpolatePath({
404
403
  path,
405
404
  params,
406
- leaveWildcards,
407
405
  leaveParams,
408
406
  decodeCharMap,
409
407
  parseCache,
@@ -446,9 +444,6 @@ export function interpolatePath({
446
444
  if (!params._splat) {
447
445
  isMissingParams = true
448
446
  // For missing splat parameters, just return the prefix and suffix without the wildcard
449
- if (leaveWildcards) {
450
- return `${segmentPrefix}${segment.value}${segmentSuffix}`
451
- }
452
447
  // If there is a prefix or suffix, return them joined, otherwise omit the segment
453
448
  if (segmentPrefix || segmentSuffix) {
454
449
  return `${segmentPrefix}${segmentSuffix}`
@@ -457,9 +452,6 @@ export function interpolatePath({
457
452
  }
458
453
 
459
454
  const value = encodeParam('_splat')
460
- if (leaveWildcards) {
461
- return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`
462
- }
463
455
  return `${segmentPrefix}${value}${segmentSuffix}`
464
456
  }
465
457
 
@@ -487,9 +479,6 @@ export function interpolatePath({
487
479
 
488
480
  // Check if optional parameter is missing or undefined
489
481
  if (!(key in params) || params[key] == null) {
490
- if (leaveWildcards) {
491
- return `${segmentPrefix}${key}${segmentSuffix}`
492
- }
493
482
  // For optional params with prefix/suffix, keep the prefix/suffix but omit the param
494
483
  if (segmentPrefix || segmentSuffix) {
495
484
  return `${segmentPrefix}${segmentSuffix}`
@@ -504,9 +493,6 @@ export function interpolatePath({
504
493
  const value = encodeParam(segment.value)
505
494
  return `${segmentPrefix}${segment.value}${value ?? ''}${segmentSuffix}`
506
495
  }
507
- if (leaveWildcards) {
508
- return `${segmentPrefix}${key}${encodeParam(key) ?? ''}${segmentSuffix}`
509
- }
510
496
  return `${segmentPrefix}${encodeParam(key) ?? ''}${segmentSuffix}`
511
497
  }
512
498
 
package/src/router.ts CHANGED
@@ -1364,13 +1364,12 @@ export class RouterCore<
1364
1364
  // Existing matches are matches that are already loaded along with
1365
1365
  // pending matches that are still loading
1366
1366
  const matchId =
1367
- interpolatePath({
1368
- path: route.id,
1369
- params: routeParams,
1370
- leaveWildcards: true,
1371
- decodeCharMap: this.pathParamsDecodeCharMap,
1372
- parseCache: this.parsePathnameCache,
1373
- }).interpolatedPath + loaderDepsHash
1367
+ // route.id for disambiguation
1368
+ route.id +
1369
+ // interpolatedPath for param changes
1370
+ interpolatedPath +
1371
+ // explicit deps
1372
+ loaderDepsHash
1374
1373
 
1375
1374
  const existingMatch = this.getMatch(matchId)
1376
1375
 
@@ -1690,7 +1689,6 @@ export class RouterCore<
1690
1689
  // This preserves the original parameter syntax including optional parameters
1691
1690
  path: nextTo,
1692
1691
  params: nextParams,
1693
- leaveWildcards: false,
1694
1692
  leaveParams: opts.leaveParams,
1695
1693
  decodeCharMap: this.pathParamsDecodeCharMap,
1696
1694
  parseCache: this.parsePathnameCache,
@@ -2295,23 +2293,27 @@ export class RouterCore<
2295
2293
  }
2296
2294
 
2297
2295
  updateMatch: UpdateMatchFn = (id, updater) => {
2298
- const matchesKey = this.state.pendingMatches?.some((d) => d.id === id)
2299
- ? 'pendingMatches'
2300
- : this.state.matches.some((d) => d.id === id)
2301
- ? 'matches'
2302
- : this.state.cachedMatches.some((d) => d.id === id)
2303
- ? 'cachedMatches'
2304
- : ''
2305
-
2306
- if (matchesKey) {
2307
- this.__store.setState((s) => ({
2308
- ...s,
2309
- [matchesKey]: s[matchesKey]?.map((d) => (d.id === id ? updater(d) : d)),
2310
- }))
2311
- }
2296
+ this.startTransition(() => {
2297
+ const matchesKey = this.state.pendingMatches?.some((d) => d.id === id)
2298
+ ? 'pendingMatches'
2299
+ : this.state.matches.some((d) => d.id === id)
2300
+ ? 'matches'
2301
+ : this.state.cachedMatches.some((d) => d.id === id)
2302
+ ? 'cachedMatches'
2303
+ : ''
2304
+
2305
+ if (matchesKey) {
2306
+ this.__store.setState((s) => ({
2307
+ ...s,
2308
+ [matchesKey]: s[matchesKey]?.map((d) =>
2309
+ d.id === id ? updater(d) : d,
2310
+ ),
2311
+ }))
2312
+ }
2313
+ })
2312
2314
  }
2313
2315
 
2314
- getMatch: GetMatchFn = (matchId: string) => {
2316
+ getMatch: GetMatchFn = (matchId: string): AnyRouteMatch | undefined => {
2315
2317
  const findFn = (d: { id: string }) => d.id === matchId
2316
2318
  return (
2317
2319
  this.state.cachedMatches.find(findFn) ??