@tanstack/react-router 1.39.8 → 1.41.0

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 (142) hide show
  1. package/dist/cjs/CatchBoundary.d.cts +2 -2
  2. package/dist/cjs/Match.cjs +238 -0
  3. package/dist/cjs/Match.cjs.map +1 -0
  4. package/dist/cjs/Match.d.cts +5 -0
  5. package/dist/cjs/Matches.cjs +8 -253
  6. package/dist/cjs/Matches.cjs.map +1 -1
  7. package/dist/cjs/Matches.d.cts +2 -11
  8. package/dist/cjs/RouterProvider.cjs.map +1 -1
  9. package/dist/cjs/RouterProvider.d.cts +3 -2
  10. package/dist/cjs/SafeFragment.cjs +8 -0
  11. package/dist/cjs/SafeFragment.cjs.map +1 -0
  12. package/dist/cjs/SafeFragment.d.cts +1 -0
  13. package/dist/cjs/ScriptOnce.cjs +28 -0
  14. package/dist/cjs/ScriptOnce.cjs.map +1 -0
  15. package/dist/cjs/ScriptOnce.d.cts +5 -0
  16. package/dist/cjs/Transitioner.cjs +2 -1
  17. package/dist/cjs/Transitioner.cjs.map +1 -1
  18. package/dist/cjs/awaited.cjs +14 -71
  19. package/dist/cjs/awaited.cjs.map +1 -1
  20. package/dist/cjs/awaited.d.cts +3 -6
  21. package/dist/cjs/defer.cjs +7 -13
  22. package/dist/cjs/defer.cjs.map +1 -1
  23. package/dist/cjs/defer.d.cts +2 -6
  24. package/dist/cjs/index.cjs +11 -7
  25. package/dist/cjs/index.cjs.map +1 -1
  26. package/dist/cjs/index.d.cts +8 -3
  27. package/dist/cjs/isServerSideError.cjs +22 -0
  28. package/dist/cjs/isServerSideError.cjs.map +1 -0
  29. package/dist/cjs/isServerSideError.d.cts +5 -0
  30. package/dist/cjs/link.cjs +8 -9
  31. package/dist/cjs/link.cjs.map +1 -1
  32. package/dist/cjs/link.d.cts +1 -0
  33. package/dist/cjs/matchContext.cjs +23 -0
  34. package/dist/cjs/matchContext.cjs.map +1 -0
  35. package/dist/cjs/matchContext.d.cts +2 -0
  36. package/dist/cjs/not-found.cjs +1 -2
  37. package/dist/cjs/not-found.cjs.map +1 -1
  38. package/dist/cjs/not-found.d.cts +2 -2
  39. package/dist/cjs/path.cjs +3 -6
  40. package/dist/cjs/path.cjs.map +1 -1
  41. package/dist/cjs/qss.cjs +3 -6
  42. package/dist/cjs/qss.cjs.map +1 -1
  43. package/dist/cjs/qss.d.cts +1 -1
  44. package/dist/cjs/redirects.cjs.map +1 -1
  45. package/dist/cjs/renderRouteNotFound.cjs +22 -0
  46. package/dist/cjs/renderRouteNotFound.cjs.map +1 -0
  47. package/dist/cjs/renderRouteNotFound.d.cts +4 -0
  48. package/dist/cjs/root.cjs.map +1 -1
  49. package/dist/cjs/root.d.cts +1 -1
  50. package/dist/cjs/route.cjs.map +1 -1
  51. package/dist/cjs/router.cjs +33 -26
  52. package/dist/cjs/router.cjs.map +1 -1
  53. package/dist/cjs/router.d.cts +16 -11
  54. package/dist/cjs/scroll-restoration.cjs +1 -2
  55. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  56. package/dist/cjs/useMatch.cjs +2 -2
  57. package/dist/cjs/useMatch.cjs.map +1 -1
  58. package/dist/cjs/utils.cjs +4 -3
  59. package/dist/cjs/utils.cjs.map +1 -1
  60. package/dist/cjs/utils.d.cts +3 -2
  61. package/dist/esm/CatchBoundary.d.ts +2 -2
  62. package/dist/esm/Match.d.ts +5 -0
  63. package/dist/esm/Match.js +221 -0
  64. package/dist/esm/Match.js.map +1 -0
  65. package/dist/esm/Matches.d.ts +2 -11
  66. package/dist/esm/Matches.js +5 -250
  67. package/dist/esm/Matches.js.map +1 -1
  68. package/dist/esm/RouterProvider.d.ts +3 -2
  69. package/dist/esm/RouterProvider.js.map +1 -1
  70. package/dist/esm/SafeFragment.d.ts +1 -0
  71. package/dist/esm/SafeFragment.js +8 -0
  72. package/dist/esm/SafeFragment.js.map +1 -0
  73. package/dist/esm/ScriptOnce.d.ts +5 -0
  74. package/dist/esm/ScriptOnce.js +28 -0
  75. package/dist/esm/ScriptOnce.js.map +1 -0
  76. package/dist/esm/Transitioner.js +2 -1
  77. package/dist/esm/Transitioner.js.map +1 -1
  78. package/dist/esm/awaited.d.ts +3 -6
  79. package/dist/esm/awaited.js +16 -73
  80. package/dist/esm/awaited.js.map +1 -1
  81. package/dist/esm/defer.d.ts +2 -6
  82. package/dist/esm/defer.js +8 -14
  83. package/dist/esm/defer.js.map +1 -1
  84. package/dist/esm/index.d.ts +8 -3
  85. package/dist/esm/index.js +9 -5
  86. package/dist/esm/index.js.map +1 -1
  87. package/dist/esm/isServerSideError.d.ts +5 -0
  88. package/dist/esm/isServerSideError.js +22 -0
  89. package/dist/esm/isServerSideError.js.map +1 -0
  90. package/dist/esm/link.d.ts +1 -0
  91. package/dist/esm/link.js +8 -9
  92. package/dist/esm/link.js.map +1 -1
  93. package/dist/esm/matchContext.d.ts +2 -0
  94. package/dist/esm/matchContext.js +6 -0
  95. package/dist/esm/matchContext.js.map +1 -0
  96. package/dist/esm/not-found.d.ts +2 -2
  97. package/dist/esm/not-found.js +1 -2
  98. package/dist/esm/not-found.js.map +1 -1
  99. package/dist/esm/path.js +3 -6
  100. package/dist/esm/path.js.map +1 -1
  101. package/dist/esm/qss.d.ts +1 -1
  102. package/dist/esm/qss.js +3 -6
  103. package/dist/esm/qss.js.map +1 -1
  104. package/dist/esm/redirects.js.map +1 -1
  105. package/dist/esm/renderRouteNotFound.d.ts +4 -0
  106. package/dist/esm/renderRouteNotFound.js +22 -0
  107. package/dist/esm/renderRouteNotFound.js.map +1 -0
  108. package/dist/esm/root.d.ts +1 -1
  109. package/dist/esm/root.js.map +1 -1
  110. package/dist/esm/route.js.map +1 -1
  111. package/dist/esm/router.d.ts +16 -11
  112. package/dist/esm/router.js +33 -26
  113. package/dist/esm/router.js.map +1 -1
  114. package/dist/esm/scroll-restoration.js +1 -2
  115. package/dist/esm/scroll-restoration.js.map +1 -1
  116. package/dist/esm/useMatch.js +1 -1
  117. package/dist/esm/useMatch.js.map +1 -1
  118. package/dist/esm/utils.d.ts +3 -2
  119. package/dist/esm/utils.js +4 -3
  120. package/dist/esm/utils.js.map +1 -1
  121. package/package.json +6 -5
  122. package/src/Match.tsx +296 -0
  123. package/src/Matches.tsx +4 -333
  124. package/src/RouterProvider.tsx +2 -1
  125. package/src/SafeFragment.tsx +5 -0
  126. package/src/ScriptOnce.tsx +27 -0
  127. package/src/Transitioner.tsx +1 -1
  128. package/src/awaited.tsx +17 -89
  129. package/src/defer.ts +9 -26
  130. package/src/index.tsx +7 -13
  131. package/src/isServerSideError.tsx +23 -0
  132. package/src/link.tsx +5 -0
  133. package/src/matchContext.tsx +3 -0
  134. package/src/not-found.tsx +1 -1
  135. package/src/qss.ts +5 -6
  136. package/src/redirects.ts +0 -1
  137. package/src/renderRouteNotFound.tsx +28 -0
  138. package/src/root.ts +1 -1
  139. package/src/route.ts +1 -1
  140. package/src/router.ts +54 -39
  141. package/src/useMatch.tsx +1 -1
  142. package/src/utils.ts +11 -9
package/src/Matches.tsx CHANGED
@@ -1,12 +1,8 @@
1
1
  import * as React from 'react'
2
- import invariant from 'tiny-invariant'
3
2
  import warning from 'tiny-warning'
4
3
  import { CatchBoundary, ErrorComponent } from './CatchBoundary'
5
4
  import { useRouterState } from './useRouterState'
6
5
  import { useRouter } from './useRouter'
7
- import { createControlledPromise, pick } from './utils'
8
- import { CatchNotFound, DefaultGlobalNotFound, isNotFound } from './not-found'
9
- import { isRedirect } from './redirects'
10
6
  import { type AnyRouter, type RegisteredRouter } from './router'
11
7
  import { Transitioner } from './Transitioner'
12
8
  import {
@@ -14,7 +10,9 @@ import {
14
10
  type ReactNode,
15
11
  type StaticDataRouteOption,
16
12
  } from './route'
17
- import { rootRouteId } from './root'
13
+ import { matchContext } from './matchContext'
14
+ import { Match } from './Match'
15
+ import { SafeFragment } from './SafeFragment'
18
16
  import type { ResolveRelativePath, ToOptions } from './link'
19
17
  import type {
20
18
  AllContext,
@@ -29,8 +27,6 @@ import type {
29
27
  } from './routeInfo'
30
28
  import type { ControlledPromise, DeepPartial, NoInfer } from './utils'
31
29
 
32
- export const matchContext = React.createContext<string | undefined>(undefined)
33
-
34
30
  export interface RouteMatch<
35
31
  TRouteId,
36
32
  TAllParams,
@@ -42,6 +38,7 @@ export interface RouteMatch<
42
38
  > {
43
39
  id: string
44
40
  routeId: TRouteId
41
+ index: number
45
42
  pathname: string
46
43
  params: TAllParams
47
44
  status: 'pending' | 'success' | 'error' | 'redirected' | 'notFound'
@@ -157,308 +154,6 @@ function MatchesInner() {
157
154
  )
158
155
  }
159
156
 
160
- function SafeFragment(props: any) {
161
- return <>{props.children}</>
162
- }
163
-
164
- export function Match({ matchId }: { matchId: string }) {
165
- const router = useRouter()
166
- const routeId = useRouterState({
167
- select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
168
- })
169
-
170
- invariant(
171
- routeId,
172
- `Could not find routeId for matchId "${matchId}". Please file an issue!`,
173
- )
174
-
175
- const route: AnyRoute = router.routesById[routeId]
176
-
177
- const PendingComponent =
178
- route.options.pendingComponent ?? router.options.defaultPendingComponent
179
-
180
- const pendingElement = PendingComponent ? <PendingComponent /> : null
181
-
182
- const routeErrorComponent =
183
- route.options.errorComponent ?? router.options.defaultErrorComponent
184
-
185
- const routeOnCatch = route.options.onCatch ?? router.options.defaultOnCatch
186
-
187
- const routeNotFoundComponent = route.isRoot
188
- ? // If it's the root route, use the globalNotFound option, with fallback to the notFoundRoute's component
189
- route.options.notFoundComponent ??
190
- router.options.notFoundRoute?.options.component
191
- : route.options.notFoundComponent
192
-
193
- const ResolvedSuspenseBoundary =
194
- // If we're on the root route, allow forcefully wrapping in suspense
195
- (!route.isRoot || route.options.wrapInSuspense) &&
196
- (route.options.wrapInSuspense ??
197
- PendingComponent ??
198
- (route.options.errorComponent as any)?.preload)
199
- ? React.Suspense
200
- : SafeFragment
201
-
202
- const ResolvedCatchBoundary = routeErrorComponent
203
- ? CatchBoundary
204
- : SafeFragment
205
-
206
- const ResolvedNotFoundBoundary = routeNotFoundComponent
207
- ? CatchNotFound
208
- : SafeFragment
209
-
210
- const resetKey = useRouterState({
211
- select: (s) => s.resolvedLocation.state.key!,
212
- })
213
-
214
- return (
215
- <matchContext.Provider value={matchId}>
216
- <ResolvedSuspenseBoundary fallback={pendingElement}>
217
- <ResolvedCatchBoundary
218
- getResetKey={() => resetKey}
219
- errorComponent={routeErrorComponent || ErrorComponent}
220
- onCatch={(error, errorInfo) => {
221
- // Forward not found errors (we don't want to show the error component for these)
222
- if (isNotFound(error)) throw error
223
- warning(false, `Error in route match: ${matchId}`)
224
- routeOnCatch?.(error, errorInfo)
225
- }}
226
- >
227
- <ResolvedNotFoundBoundary
228
- fallback={(error) => {
229
- // If the current not found handler doesn't exist or it has a
230
- // route ID which doesn't match the current route, rethrow the error
231
- if (
232
- !routeNotFoundComponent ||
233
- (error.routeId && error.routeId !== routeId) ||
234
- (!error.routeId && !route.isRoot)
235
- )
236
- throw error
237
-
238
- return React.createElement(routeNotFoundComponent, error as any)
239
- }}
240
- >
241
- <MatchInner matchId={matchId} />
242
- </ResolvedNotFoundBoundary>
243
- </ResolvedCatchBoundary>
244
- </ResolvedSuspenseBoundary>
245
- </matchContext.Provider>
246
- )
247
- }
248
-
249
- function MatchInner({ matchId }: { matchId: string }): any {
250
- const router = useRouter()
251
- const routeId = useRouterState({
252
- select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
253
- })
254
-
255
- const route = router.routesById[routeId]!
256
-
257
- const match = useRouterState({
258
- select: (s) =>
259
- pick(s.matches.find((d) => d.id === matchId)!, [
260
- 'id',
261
- 'status',
262
- 'error',
263
- 'loadPromise',
264
- 'minPendingPromise',
265
- ]),
266
- })
267
-
268
- const RouteErrorComponent =
269
- (route.options.errorComponent ?? router.options.defaultErrorComponent) ||
270
- ErrorComponent
271
-
272
- if (match.status === 'notFound') {
273
- let error: unknown
274
- if (isServerSideError(match.error)) {
275
- const deserializeError =
276
- router.options.errorSerializer?.deserialize ?? defaultDeserializeError
277
-
278
- error = deserializeError(match.error.data)
279
- } else {
280
- error = match.error
281
- }
282
-
283
- invariant(isNotFound(error), 'Expected a notFound error')
284
-
285
- return renderRouteNotFound(router, route, error)
286
- }
287
-
288
- if (match.status === 'redirected') {
289
- // Redirects should be handled by the router transition. If we happen to
290
- // encounter a redirect here, it's a bug. Let's warn, but render nothing.
291
- invariant(isRedirect(match.error), 'Expected a redirect error')
292
-
293
- // warning(
294
- // false,
295
- // 'Tried to render a redirected route match! This is a weird circumstance, please file an issue!',
296
- // )
297
-
298
- throw match.loadPromise
299
- }
300
-
301
- if (match.status === 'error') {
302
- // If we're on the server, we need to use React's new and super
303
- // wonky api for throwing errors from a server side render inside
304
- // of a suspense boundary. This is the only way to get
305
- // renderToPipeableStream to not hang indefinitely.
306
- // We'll serialize the error and rethrow it on the client.
307
- if (router.isServer) {
308
- return (
309
- <RouteErrorComponent
310
- error={match.error}
311
- info={{
312
- componentStack: '',
313
- }}
314
- />
315
- )
316
- }
317
-
318
- if (isServerSideError(match.error)) {
319
- const deserializeError =
320
- router.options.errorSerializer?.deserialize ?? defaultDeserializeError
321
- throw deserializeError(match.error.data)
322
- } else {
323
- throw match.error
324
- }
325
- }
326
-
327
- if (match.status === 'pending') {
328
- // We're pending, and if we have a minPendingMs, we need to wait for it
329
- const pendingMinMs =
330
- route.options.pendingMinMs ?? router.options.defaultPendingMinMs
331
-
332
- if (pendingMinMs && !match.minPendingPromise) {
333
- // Create a promise that will resolve after the minPendingMs
334
-
335
- match.minPendingPromise = createControlledPromise()
336
-
337
- if (!router.isServer) {
338
- Promise.resolve().then(() => {
339
- router.__store.setState((s) => ({
340
- ...s,
341
- matches: s.matches.map((d) =>
342
- d.id === match.id
343
- ? { ...d, minPendingPromise: createControlledPromise() }
344
- : d,
345
- ),
346
- }))
347
- })
348
-
349
- setTimeout(() => {
350
- // We've handled the minPendingPromise, so we can delete it
351
- router.__store.setState((s) => {
352
- return {
353
- ...s,
354
- matches: s.matches.map((d) =>
355
- d.id === match.id
356
- ? {
357
- ...d,
358
- minPendingPromise:
359
- (d.minPendingPromise?.resolve(), undefined),
360
- }
361
- : d,
362
- ),
363
- }
364
- })
365
- }, pendingMinMs)
366
- }
367
- }
368
-
369
- throw match.loadPromise
370
- }
371
-
372
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
373
- if (match.status === 'success') {
374
- const Comp = route.options.component ?? router.options.defaultComponent
375
-
376
- if (Comp) {
377
- return <Comp />
378
- }
379
-
380
- return <Outlet />
381
- }
382
-
383
- invariant(
384
- false,
385
- 'Idle routeMatch status encountered during rendering! You should never see this. File an issue!',
386
- )
387
- }
388
-
389
- export const Outlet = React.memo(function Outlet() {
390
- const router = useRouter()
391
- const matchId = React.useContext(matchContext)
392
- const routeId = useRouterState({
393
- select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
394
- })
395
-
396
- const route = router.routesById[routeId]!
397
-
398
- const { parentGlobalNotFound } = useRouterState({
399
- select: (s) => {
400
- const matches = s.matches
401
- const parentMatch = matches.find((d) => d.id === matchId)
402
- invariant(
403
- parentMatch,
404
- `Could not find parent match for matchId "${matchId}"`,
405
- )
406
- return {
407
- parentGlobalNotFound: parentMatch.globalNotFound,
408
- }
409
- },
410
- })
411
-
412
- const childMatchId = useRouterState({
413
- select: (s) => {
414
- const matches = s.matches
415
- const index = matches.findIndex((d) => d.id === matchId)
416
- return matches[index + 1]?.id
417
- },
418
- })
419
-
420
- if (parentGlobalNotFound) {
421
- return renderRouteNotFound(router, route, undefined)
422
- }
423
-
424
- if (!childMatchId) {
425
- return null
426
- }
427
-
428
- const nextMatch = <Match matchId={childMatchId} />
429
-
430
- const pendingElement = router.options.defaultPendingComponent ? (
431
- <router.options.defaultPendingComponent />
432
- ) : null
433
-
434
- if (matchId === rootRouteId) {
435
- return (
436
- <React.Suspense fallback={pendingElement}>{nextMatch}</React.Suspense>
437
- )
438
- }
439
-
440
- return nextMatch
441
- })
442
-
443
- function renderRouteNotFound(router: AnyRouter, route: AnyRoute, data: any) {
444
- if (!route.options.notFoundComponent) {
445
- if (router.options.defaultNotFoundComponent) {
446
- return <router.options.defaultNotFoundComponent data={data} />
447
- }
448
-
449
- if (process.env.NODE_ENV === 'development') {
450
- warning(
451
- route.options.notFoundComponent,
452
- `A notFoundError was encountered on the route with ID "${route.id}", but a notFoundComponent option was not configured, nor was a router level defaultNotFoundComponent configured. Consider configuring at least one of these to avoid TanStack Router's overly generic defaultNotFoundComponent (<div>Not Found<div>)`,
453
- )
454
- }
455
-
456
- return <DefaultGlobalNotFound />
457
- }
458
-
459
- return <route.options.notFoundComponent data={data} />
460
- }
461
-
462
157
  export interface MatchRouteOptions {
463
158
  pending?: boolean
464
159
  caseSensitive?: boolean
@@ -608,27 +303,3 @@ export function useChildMatches<
608
303
  },
609
304
  })
610
305
  }
611
-
612
- export function isServerSideError(error: unknown): error is {
613
- __isServerError: true
614
- data: Record<string, any>
615
- } {
616
- if (!(typeof error === 'object' && error && 'data' in error)) return false
617
- if (!('__isServerError' in error && error.__isServerError)) return false
618
- if (!(typeof error.data === 'object' && error.data)) return false
619
-
620
- return error.__isServerError === true
621
- }
622
-
623
- export function defaultDeserializeError(serializedData: Record<string, any>) {
624
- if ('name' in serializedData && 'message' in serializedData) {
625
- const error = new Error(serializedData.message)
626
- error.name = serializedData.name
627
- if (process.env.NODE_ENV === 'development') {
628
- error.stack = serializedData.stack
629
- }
630
- return error
631
- }
632
-
633
- return serializedData.data
634
- }
@@ -1,4 +1,4 @@
1
- // eslint-disable-next-line @typescript-eslint/consistent-type-imports
1
+ // eslint-disable-next-line ts/consistent-type-imports
2
2
  import * as React from 'react'
3
3
  import { Matches } from './Matches'
4
4
  import { getRouterContext } from './routerContext'
@@ -24,6 +24,7 @@ export interface CommitLocationOptions {
24
24
  * @deprecated All navigations use React transitions under the hood now
25
25
  **/
26
26
  startTransition?: boolean
27
+ ignoreBlocker?: boolean
27
28
  }
28
29
 
29
30
  export interface MatchLocation {
@@ -0,0 +1,5 @@
1
+ import * as React from 'react'
2
+
3
+ export function SafeFragment(props: any) {
4
+ return <>{props.children}</>
5
+ }
@@ -0,0 +1,27 @@
1
+ export function ScriptOnce({
2
+ className,
3
+ children,
4
+ log,
5
+ ...rest
6
+ }: { children: string; log?: boolean } & React.HTMLProps<HTMLScriptElement>) {
7
+ if (typeof document !== 'undefined') {
8
+ return null
9
+ }
10
+
11
+ return (
12
+ <script
13
+ {...rest}
14
+ className={`tsr-once ${className || ''}`}
15
+ dangerouslySetInnerHTML={{
16
+ __html: [
17
+ children,
18
+ (log ?? true) && process.env.NODE_ENV === 'development'
19
+ ? `console.info('ScriptOnce', ${JSON.stringify(children)})`
20
+ : '',
21
+ ]
22
+ .filter(Boolean)
23
+ .join('\n'),
24
+ }}
25
+ />
26
+ )
27
+ }
@@ -52,7 +52,7 @@ export function Transitioner() {
52
52
  // Try to load the initial location
53
53
  useLayoutEffect(() => {
54
54
  if (
55
- window.__TSR_DEHYDRATED__ ||
55
+ window.__TSR__?.dehydrated ||
56
56
  (mountLoadForRouter.current.router === router &&
57
57
  mountLoadForRouter.current.mounted)
58
58
  ) {
package/src/awaited.tsx CHANGED
@@ -2,86 +2,50 @@ import * as React from 'react'
2
2
  import warning from 'tiny-warning'
3
3
  import { useRouter } from './useRouter'
4
4
  import { defaultSerializeError } from './router'
5
- import { isDehydratedDeferred } from './defer'
6
- import { defaultDeserializeError, isServerSideError } from './Matches'
5
+ import { defer } from './defer'
6
+ import { defaultDeserializeError, isServerSideError } from './isServerSideError'
7
7
  import type { DeferredPromise } from './defer'
8
8
 
9
9
  export type AwaitOptions<T> = {
10
- promise: DeferredPromise<T>
10
+ promise: Promise<T>
11
11
  }
12
12
 
13
13
  export function useAwaited<T>({
14
- promise,
14
+ promise: _promise,
15
15
  }: AwaitOptions<T>): [T, DeferredPromise<T>] {
16
16
  const router = useRouter()
17
- // const rerender = React.useReducer((x) => x + 1, 0)[1]
17
+ const promise = _promise as DeferredPromise<T>
18
18
 
19
- const state = promise.__deferredState
19
+ defer(promise)
20
20
 
21
- // Dehydrated promises only
22
- // Successful or errored deferred promises mean they
23
- // were resolved on the server and no further action is needed
24
- if (isDehydratedDeferred(promise) && state.status === 'pending') {
25
- const streamedData = (window as any)[`__TSR__DEFERRED__${state.uid}`]
26
-
27
- if (streamedData) {
28
- Object.assign(state, router.options.transformer.parse(streamedData))
29
- } else {
30
- let token = router.registeredDeferredsIds.get(state.uid)
31
-
32
- // If we haven't yet, create a promise and resolver that our streamed HTML can use
33
- // when the client-side data is streamed in and ready.
34
- if (!token) {
35
- token = {}
36
- router.registeredDeferredsIds.set(state.uid, token)
37
- router.registeredDeferreds.set(token, state)
38
-
39
- Object.assign(state, {
40
- resolve: () => {
41
- state.__resolvePromise?.()
42
- // rerender()
43
- },
44
- promise: new Promise((r) => {
45
- state.__resolvePromise = r as any
46
- }),
47
- __resolvePromise: () => {},
48
- })
49
- }
50
- }
21
+ if (promise.status === 'pending') {
22
+ throw promise
51
23
  }
52
24
 
53
- // If the promise is pending, always throw the state.promise
54
- // For originating promises, this will be the original promise
55
- // For dehydrated promises, this will be the placeholder promise
56
- // that will be resolved when the server sends the real data
57
- if (state.status === 'pending') {
58
- throw isDehydratedDeferred(promise) ? state.promise : promise
59
- }
60
-
61
- if (state.status === 'error') {
25
+ if (promise.status === 'error') {
62
26
  if (typeof document !== 'undefined') {
63
- if (isServerSideError(state.error)) {
27
+ if (isServerSideError(promise.error)) {
64
28
  throw (
65
29
  router.options.errorSerializer?.deserialize ?? defaultDeserializeError
66
- )(state.error.data as any)
30
+ )(promise.error.data as any)
67
31
  } else {
68
32
  warning(
69
33
  false,
70
34
  "Encountered a server-side error that doesn't fit the expected shape",
71
35
  )
72
- throw state.error
36
+ throw promise.error
73
37
  }
74
38
  } else {
75
39
  throw {
76
40
  data: (
77
41
  router.options.errorSerializer?.serialize ?? defaultSerializeError
78
- )(state.error),
42
+ )(promise.error),
79
43
  __isServerError: true,
80
44
  }
81
45
  }
82
46
  }
83
47
 
84
- return [promise.__deferredState.data as any, promise]
48
+ return [promise.data as any, promise]
85
49
  }
86
50
 
87
51
  export function Await<T>(
@@ -102,43 +66,7 @@ function AwaitInner<T>(
102
66
  fallback?: React.ReactNode
103
67
  children: (result: T) => React.ReactNode
104
68
  },
105
- ) {
106
- const router = useRouter()
107
- const [data, promise] = useAwaited(props)
108
- const state = promise.__deferredState
109
- // If we are the originator of the promise,
110
- // inject the state into the HTML stream
111
- return (
112
- <>
113
- {!isDehydratedDeferred(promise) ? (
114
- <ScriptOnce
115
- children={`window.__TSR__DEFERRED__${state.uid} = ${JSON.stringify(router.options.transformer.stringify(state))}
116
- if (window.__TSR__ROUTER__) {
117
- let deferred = window.__TSR__ROUTER__.getDeferred('${state.uid}');
118
- if (deferred) deferred.resolve(window.__TSR__DEFERRED__${state.uid});
119
- }`}
120
- />
121
- ) : null}
122
- {props.children(data)}
123
- </>
124
- )
125
- }
126
-
127
- export function ScriptOnce({
128
- className,
129
- children,
130
- ...rest
131
- }: { children: string } & React.HTMLProps<HTMLScriptElement>) {
132
- return (
133
- <script
134
- {...rest}
135
- className={`tsr-script-once ${className || ''}`}
136
- dangerouslySetInnerHTML={{
137
- __html: [
138
- children,
139
- `document.querySelectorAll('.tsr-script-once').forEach((el) => el.parentElement.removeChild(el));`,
140
- ].join('\n'),
141
- }}
142
- />
143
- )
69
+ ): React.JSX.Element {
70
+ const [data] = useAwaited(props)
71
+ return props.children(data) as React.JSX.Element
144
72
  }
package/src/defer.ts CHANGED
@@ -3,8 +3,7 @@ import { defaultSerializeError } from './router'
3
3
  export type DeferredPromiseState<T> = {
4
4
  uid: string
5
5
  resolve?: () => void
6
- promise?: Promise<void>
7
- __resolvePromise?: () => void
6
+ reject?: () => void
8
7
  } & (
9
8
  | {
10
9
  status: 'pending'
@@ -22,9 +21,7 @@ export type DeferredPromiseState<T> = {
22
21
  }
23
22
  )
24
23
 
25
- export type DeferredPromise<T> = Promise<T> & {
26
- __deferredState: DeferredPromiseState<T>
27
- }
24
+ export type DeferredPromise<T> = Promise<T> & DeferredPromiseState<T>
28
25
 
29
26
  export function defer<T>(
30
27
  _promise: Promise<T>,
@@ -34,23 +31,19 @@ export function defer<T>(
34
31
  ) {
35
32
  const promise = _promise as DeferredPromise<T>
36
33
 
37
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
38
- if (!promise.__deferredState) {
39
- promise.__deferredState = {
40
- uid: Math.random().toString(36).slice(2),
34
+ if (!(promise as any).status) {
35
+ Object.assign(promise, {
41
36
  status: 'pending',
42
- }
43
-
44
- const state = promise.__deferredState
37
+ })
45
38
 
46
39
  promise
47
40
  .then((data) => {
48
- state.status = 'success' as any
49
- state.data = data
41
+ promise.status = 'success' as any
42
+ promise.data = data
50
43
  })
51
44
  .catch((error) => {
52
- state.status = 'error' as any
53
- state.error = {
45
+ promise.status = 'error' as any
46
+ ;(promise as any).error = {
54
47
  data: (options?.serializeError ?? defaultSerializeError)(error),
55
48
  __isServerError: true,
56
49
  }
@@ -59,13 +52,3 @@ export function defer<T>(
59
52
 
60
53
  return promise
61
54
  }
62
-
63
- export function isDehydratedDeferred(obj: any): boolean {
64
- return (
65
- typeof obj === 'object' &&
66
- obj !== null &&
67
- !(obj instanceof Promise) &&
68
- !obj.then &&
69
- '__deferredState' in obj
70
- )
71
- }
package/src/index.tsx CHANGED
@@ -1,4 +1,3 @@
1
- //
2
1
  export {
3
2
  createHistory,
4
3
  createBrowserHistory,
@@ -12,13 +11,9 @@ export {
12
11
  } from '@tanstack/history'
13
12
  export { default as invariant } from 'tiny-invariant'
14
13
  export { default as warning } from 'tiny-warning'
15
- export { useAwaited, Await, type AwaitOptions, ScriptOnce } from './awaited'
16
- export {
17
- defer,
18
- isDehydratedDeferred,
19
- type DeferredPromiseState,
20
- type DeferredPromise,
21
- } from './defer'
14
+ export { useAwaited, Await, type AwaitOptions } from './awaited'
15
+ export { ScriptOnce } from './ScriptOnce'
16
+ export { defer, type DeferredPromiseState, type DeferredPromise } from './defer'
22
17
  export { CatchBoundary, ErrorComponent } from './CatchBoundary'
23
18
  export {
24
19
  FileRoute,
@@ -68,23 +63,21 @@ export {
68
63
  } from './link'
69
64
  export { type ParsedLocation } from './location'
70
65
  export {
71
- matchContext,
72
66
  Matches,
73
- Match,
74
- Outlet,
75
67
  useMatchRoute,
76
68
  MatchRoute,
77
69
  useMatches,
78
70
  useParentMatches,
79
71
  useChildMatches,
80
- isServerSideError,
81
- defaultDeserializeError,
82
72
  type RouteMatch,
83
73
  type AnyRouteMatch,
84
74
  type MatchRouteOptions,
85
75
  type UseMatchRouteOptions,
86
76
  type MakeMatchRouteOptions,
87
77
  } from './Matches'
78
+ export { matchContext } from './matchContext'
79
+ export { Match, Outlet } from './Match'
80
+ export { isServerSideError, defaultDeserializeError } from './isServerSideError'
88
81
  export { useMatch } from './useMatch'
89
82
  export { useLoaderDeps } from './useLoaderDeps'
90
83
  export { useLoaderData } from './useLoaderData'
@@ -268,3 +261,4 @@ export {
268
261
  type NotFoundError,
269
262
  } from './not-found'
270
263
  export { type Manifest, type RouterManagedTag } from './manifest'
264
+ export { createControlledPromise, type ControlledPromise } from './utils'