@tanstack/react-router 1.28.1 → 1.28.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.
Files changed (45) hide show
  1. package/dist/cjs/Matches.cjs +45 -23
  2. package/dist/cjs/Matches.cjs.map +1 -1
  3. package/dist/cjs/Matches.d.cts +5 -6
  4. package/dist/cjs/fileRoute.d.cts +4 -4
  5. package/dist/cjs/index.cjs +0 -1
  6. package/dist/cjs/index.cjs.map +1 -1
  7. package/dist/cjs/index.d.cts +1 -1
  8. package/dist/cjs/link.cjs.map +1 -1
  9. package/dist/cjs/link.d.cts +2 -0
  10. package/dist/cjs/redirects.cjs.map +1 -1
  11. package/dist/cjs/redirects.d.cts +3 -1
  12. package/dist/cjs/route.cjs.map +1 -1
  13. package/dist/cjs/route.d.cts +2 -2
  14. package/dist/cjs/router.cjs +374 -327
  15. package/dist/cjs/router.cjs.map +1 -1
  16. package/dist/cjs/router.d.cts +2 -2
  17. package/dist/cjs/utils.cjs +20 -2
  18. package/dist/cjs/utils.cjs.map +1 -1
  19. package/dist/cjs/utils.d.cts +6 -1
  20. package/dist/esm/Matches.d.ts +5 -6
  21. package/dist/esm/Matches.js +46 -24
  22. package/dist/esm/Matches.js.map +1 -1
  23. package/dist/esm/fileRoute.d.ts +4 -4
  24. package/dist/esm/index.d.ts +1 -1
  25. package/dist/esm/index.js +1 -2
  26. package/dist/esm/link.d.ts +2 -0
  27. package/dist/esm/link.js.map +1 -1
  28. package/dist/esm/redirects.d.ts +3 -1
  29. package/dist/esm/redirects.js.map +1 -1
  30. package/dist/esm/route.d.ts +2 -2
  31. package/dist/esm/route.js.map +1 -1
  32. package/dist/esm/router.d.ts +2 -2
  33. package/dist/esm/router.js +375 -328
  34. package/dist/esm/router.js.map +1 -1
  35. package/dist/esm/utils.d.ts +6 -1
  36. package/dist/esm/utils.js +20 -2
  37. package/dist/esm/utils.js.map +1 -1
  38. package/package.json +4 -2
  39. package/src/Matches.tsx +73 -35
  40. package/src/index.tsx +0 -1
  41. package/src/link.tsx +2 -0
  42. package/src/redirects.ts +4 -2
  43. package/src/route.ts +2 -7
  44. package/src/router.ts +498 -426
  45. package/src/utils.ts +31 -2
package/src/Matches.tsx CHANGED
@@ -1,12 +1,18 @@
1
1
  import * as React from 'react'
2
2
  import invariant from 'tiny-invariant'
3
3
  import warning from 'tiny-warning'
4
+ import { set } from 'zod'
4
5
  import { CatchBoundary, ErrorComponent } from './CatchBoundary'
5
6
  import { useRouterState } from './useRouterState'
6
7
  import { useRouter } from './useRouter'
7
- import { isServer, pick } from './utils'
8
+ import { createControlledPromise, pick } from './utils'
8
9
  import { CatchNotFound, DefaultGlobalNotFound, isNotFound } from './not-found'
9
10
  import { isRedirect } from './redirects'
11
+ import {
12
+ type AnyRouter,
13
+ type RegisteredRouter,
14
+ type RouterState,
15
+ } from './router'
10
16
  import type { ResolveRelativePath, ToOptions } from './link'
11
17
  import type {
12
18
  AnyRoute,
@@ -23,8 +29,13 @@ import type {
23
29
  RouteIds,
24
30
  RoutePaths,
25
31
  } from './routeInfo'
26
- import type { AnyRouter, RegisteredRouter, RouterState } from './router'
27
- import type { DeepPartial, Expand, NoInfer, StrictOrFrom } from './utils'
32
+ import type {
33
+ ControlledPromise,
34
+ DeepPartial,
35
+ Expand,
36
+ NoInfer,
37
+ StrictOrFrom,
38
+ } from './utils'
28
39
 
29
40
  export const matchContext = React.createContext<string | undefined>(undefined)
30
41
 
@@ -41,12 +52,12 @@ export interface RouteMatch<
41
52
  : Expand<Partial<AllParams<TRouteTree>>>
42
53
  status: 'pending' | 'success' | 'error' | 'redirected' | 'notFound'
43
54
  isFetching: boolean
44
- showPending: boolean
45
55
  error: unknown
46
56
  paramsError: unknown
47
57
  searchError: unknown
48
58
  updatedAt: number
49
- loadPromise?: Promise<void>
59
+ loadPromise: ControlledPromise<void>
60
+ loaderPromise: Promise<RouteById<TRouteTree, TRouteId>['types']['loaderData']>
50
61
  loaderData?: RouteById<TRouteTree, TRouteId>['types']['loaderData']
51
62
  routeContext: RouteById<TRouteTree, TRouteId>['types']['routeContext']
52
63
  context: RouteById<TRouteTree, TRouteId>['types']['allContext']
@@ -64,22 +75,21 @@ export interface RouteMatch<
64
75
  loaderDeps: RouteById<TRouteTree, TRouteId>['types']['loaderDeps']
65
76
  preload: boolean
66
77
  invalid: boolean
67
- pendingPromise?: Promise<void>
68
78
  meta?: Array<JSX.IntrinsicElements['meta']>
69
79
  links?: Array<JSX.IntrinsicElements['link']>
70
80
  scripts?: Array<JSX.IntrinsicElements['script']>
71
81
  headers?: Record<string, string>
72
82
  globalNotFound?: boolean
73
83
  staticData: StaticDataRouteOption
84
+ minPendingPromise?: ControlledPromise<void>
74
85
  }
75
86
 
76
87
  export type AnyRouteMatch = RouteMatch<any, any>
77
88
 
78
89
  export function Matches() {
79
- const router = useRouter()
80
90
  const matchId = useRouterState({
81
91
  select: (s) => {
82
- return getRenderedMatches(s)[0]?.id
92
+ return s.matches[0]?.id
83
93
  },
84
94
  })
85
95
 
@@ -113,8 +123,7 @@ function SafeFragment(props: any) {
113
123
  export function Match({ matchId }: { matchId: string }) {
114
124
  const router = useRouter()
115
125
  const routeId = useRouterState({
116
- select: (s) =>
117
- getRenderedMatches(s).find((d) => d.id === matchId)?.routeId as string,
126
+ select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
118
127
  })
119
128
 
120
129
  invariant(
@@ -186,7 +195,7 @@ export function Match({ matchId }: { matchId: string }) {
186
195
  return React.createElement(routeNotFoundComponent, error as any)
187
196
  }}
188
197
  >
189
- <MatchInner matchId={matchId} pendingElement={pendingElement} />
198
+ <MatchInner matchId={matchId} />
190
199
  </ResolvedNotFoundBoundary>
191
200
  </ResolvedCatchBoundary>
192
201
  </ResolvedSuspenseBoundary>
@@ -196,26 +205,26 @@ export function Match({ matchId }: { matchId: string }) {
196
205
 
197
206
  function MatchInner({
198
207
  matchId,
199
- pendingElement,
208
+ // pendingElement,
200
209
  }: {
201
210
  matchId: string
202
- pendingElement: any
211
+ // pendingElement: any
203
212
  }): any {
204
213
  const router = useRouter()
205
214
  const routeId = useRouterState({
206
- select: (s) =>
207
- getRenderedMatches(s).find((d) => d.id === matchId)?.routeId as string,
215
+ select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
208
216
  })
209
217
 
210
218
  const route = router.routesById[routeId]!
211
219
 
212
220
  const match = useRouterState({
213
221
  select: (s) =>
214
- pick(getRenderedMatches(s).find((d) => d.id === matchId)!, [
222
+ pick(s.matches.find((d) => d.id === matchId)!, [
223
+ 'id',
215
224
  'status',
216
225
  'error',
217
- 'showPending',
218
226
  'loadPromise',
227
+ 'minPendingPromise',
219
228
  ]),
220
229
  })
221
230
 
@@ -258,7 +267,7 @@ function MatchInner({
258
267
  // of a suspense boundary. This is the only way to get
259
268
  // renderToPipeableStream to not hang indefinitely.
260
269
  // We'll serialize the error and rethrow it on the client.
261
- if (isServer) {
270
+ if (router.isServer) {
262
271
  return (
263
272
  <RouteErrorComponent
264
273
  error={match.error}
@@ -279,9 +288,47 @@ function MatchInner({
279
288
  }
280
289
 
281
290
  if (match.status === 'pending') {
282
- if (match.showPending) {
283
- return pendingElement
291
+ // We're pending, and if we have a minPendingMs, we need to wait for it
292
+ const pendingMinMs =
293
+ route.options.pendingMinMs ?? router.options.defaultPendingMinMs
294
+
295
+ if (pendingMinMs && !match.minPendingPromise) {
296
+ // Create a promise that will resolve after the minPendingMs
297
+
298
+ match.minPendingPromise = createControlledPromise()
299
+
300
+ if (!router.isServer) {
301
+ Promise.resolve().then(() => {
302
+ router.__store.setState((s) => ({
303
+ ...s,
304
+ matches: s.matches.map((d) =>
305
+ d.id === match.id
306
+ ? { ...d, minPendingPromise: createControlledPromise() }
307
+ : d,
308
+ ),
309
+ }))
310
+ })
311
+
312
+ setTimeout(() => {
313
+ // We've handled the minPendingPromise, so we can delete it
314
+ router.__store.setState((s) => {
315
+ return {
316
+ ...s,
317
+ matches: s.matches.map((d) =>
318
+ d.id === match.id
319
+ ? {
320
+ ...d,
321
+ minPendingPromise:
322
+ (d.minPendingPromise?.resolve(), undefined),
323
+ }
324
+ : d,
325
+ ),
326
+ }
327
+ })
328
+ }, pendingMinMs)
329
+ }
284
330
  }
331
+
285
332
  throw match.loadPromise
286
333
  }
287
334
 
@@ -306,15 +353,14 @@ export const Outlet = React.memo(function Outlet() {
306
353
  const router = useRouter()
307
354
  const matchId = React.useContext(matchContext)
308
355
  const routeId = useRouterState({
309
- select: (s) =>
310
- getRenderedMatches(s).find((d) => d.id === matchId)?.routeId as string,
356
+ select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
311
357
  })
312
358
 
313
359
  const route = router.routesById[routeId]!
314
360
 
315
361
  const { parentGlobalNotFound } = useRouterState({
316
362
  select: (s) => {
317
- const matches = getRenderedMatches(s)
363
+ const matches = s.matches
318
364
  const parentMatch = matches.find((d) => d.id === matchId)
319
365
  invariant(
320
366
  parentMatch,
@@ -328,7 +374,7 @@ export const Outlet = React.memo(function Outlet() {
328
374
 
329
375
  const childMatchId = useRouterState({
330
376
  select: (s) => {
331
- const matches = getRenderedMatches(s)
377
+ const matches = s.matches
332
378
  const index = matches.findIndex((d) => d.id === matchId)
333
379
  return matches[index + 1]?.id
334
380
  },
@@ -453,14 +499,6 @@ export function MatchRoute<
453
499
  return params ? props.children : null
454
500
  }
455
501
 
456
- export function getRenderedMatches<
457
- TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
458
- >(state: RouterState<TRouteTree>) {
459
- return state.pendingMatches?.some((d) => d.showPending)
460
- ? state.pendingMatches
461
- : state.matches
462
- }
463
-
464
502
  export function useMatch<
465
503
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
466
504
  TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
@@ -476,7 +514,7 @@ export function useMatch<
476
514
 
477
515
  const matchSelection = useRouterState({
478
516
  select: (state) => {
479
- const match = getRenderedMatches(state).find((d) =>
517
+ const match = state.matches.find((d) =>
480
518
  opts.from ? opts.from === d.routeId : d.id === nearestMatchId,
481
519
  )
482
520
 
@@ -491,7 +529,7 @@ export function useMatch<
491
529
  },
492
530
  })
493
531
 
494
- return matchSelection as any
532
+ return matchSelection as TSelected
495
533
  }
496
534
 
497
535
  export function useMatches<
@@ -506,7 +544,7 @@ export function useMatches<
506
544
  }): T {
507
545
  return useRouterState({
508
546
  select: (state) => {
509
- const matches = getRenderedMatches(state)
547
+ const matches = state.matches
510
548
  return opts?.select
511
549
  ? opts.select(matches as Array<TRouteMatch>)
512
550
  : (matches as T)
package/src/index.tsx CHANGED
@@ -89,7 +89,6 @@ export {
89
89
  useLoaderData,
90
90
  isServerSideError,
91
91
  defaultDeserializeError,
92
- getRenderedMatches,
93
92
  type RouteMatch,
94
93
  type AnyRouteMatch,
95
94
  type MatchRouteOptions,
package/src/link.tsx CHANGED
@@ -3,6 +3,7 @@ import { useMatch } from './Matches'
3
3
  import { useRouterState } from './useRouterState'
4
4
  import { useRouter } from './useRouter'
5
5
  import { deepEqual, exactPathTest, functionalUpdate } from './utils'
6
+ import type { ParsedLocation } from '.'
6
7
  import type { HistoryState } from '@tanstack/history'
7
8
  import type { Trim } from './fileRoute'
8
9
  import type { AnyRoute, RootSearchSchema } from './route'
@@ -178,6 +179,7 @@ export type ToOptions<
178
179
  TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
179
180
  TMaskTo extends string = '',
180
181
  > = ToSubOptions<TRouteTree, TFrom, TTo> & {
182
+ _fromLocation?: ParsedLocation
181
183
  mask?: ToMaskOptions<TRouteTree, TMaskFrom, TMaskTo>
182
184
  }
183
185
 
package/src/redirects.ts CHANGED
@@ -31,8 +31,10 @@ export type ResolvedRedirect<
31
31
  TMaskTo extends string = '',
32
32
  > = PickAsRequired<
33
33
  Redirect<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
34
- 'code' | 'statusCode' | 'href' | 'headers'
35
- >
34
+ 'code' | 'statusCode' | 'headers'
35
+ > & {
36
+ href: string
37
+ }
36
38
 
37
39
  export function redirect<
38
40
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
package/src/route.ts CHANGED
@@ -254,14 +254,10 @@ export type UpdatableRouteOptions<TAllParams, TFullSearchSchema, TLoaderData> =
254
254
  meta?: (ctx: {
255
255
  params: TAllParams
256
256
  loaderData: TLoaderData
257
- }) =>
258
- | Array<JSX.IntrinsicElements['meta']>
259
- | Promise<Array<JSX.IntrinsicElements['meta']>>
257
+ }) => Array<JSX.IntrinsicElements['meta']>
260
258
  links?: () => Array<JSX.IntrinsicElements['link']>
261
259
  scripts?: () => Array<JSX.IntrinsicElements['script']>
262
- headers?: (ctx: {
263
- loaderData: TLoaderData
264
- }) => Promise<Record<string, string>> | Record<string, string>
260
+ headers?: (ctx: { loaderData: TLoaderData }) => Record<string, string>
265
261
  } & UpdatableStaticRouteOption
266
262
 
267
263
  export type UpdatableStaticRouteOption =
@@ -822,7 +818,6 @@ export class Route<
822
818
  }
823
819
 
824
820
  useMatch = <
825
- // eslint-disable-next-line no-shadow
826
821
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
827
822
  TRouteMatchState = RouteMatch<TRouteTree, TId>,
828
823
  TSelected = TRouteMatchState,