@tanstack/solid-router 1.166.7 → 2.0.0-alpha.1

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 (182) hide show
  1. package/dist/cjs/Asset.cjs +111 -68
  2. package/dist/cjs/Asset.cjs.map +1 -1
  3. package/dist/cjs/CatchBoundary.cjs +15 -15
  4. package/dist/cjs/CatchBoundary.cjs.map +1 -1
  5. package/dist/cjs/ClientOnly.cjs +2 -2
  6. package/dist/cjs/ClientOnly.cjs.map +1 -1
  7. package/dist/cjs/HeadContent.cjs +16 -8
  8. package/dist/cjs/HeadContent.cjs.map +1 -1
  9. package/dist/cjs/HeadContent.dev.cjs +18 -10
  10. package/dist/cjs/HeadContent.dev.cjs.map +1 -1
  11. package/dist/cjs/Match.cjs +125 -81
  12. package/dist/cjs/Match.cjs.map +1 -1
  13. package/dist/cjs/Matches.cjs +23 -16
  14. package/dist/cjs/Matches.cjs.map +1 -1
  15. package/dist/cjs/RouterProvider.cjs +3 -2
  16. package/dist/cjs/RouterProvider.cjs.map +1 -1
  17. package/dist/cjs/SafeFragment.cjs +1 -1
  18. package/dist/cjs/ScriptOnce.cjs +4 -2
  19. package/dist/cjs/ScriptOnce.cjs.map +1 -1
  20. package/dist/cjs/Scripts.cjs +6 -2
  21. package/dist/cjs/Scripts.cjs.map +1 -1
  22. package/dist/cjs/Transitioner.cjs +11 -16
  23. package/dist/cjs/Transitioner.cjs.map +1 -1
  24. package/dist/cjs/awaited.cjs +20 -16
  25. package/dist/cjs/awaited.cjs.map +1 -1
  26. package/dist/cjs/lazyRouteComponent.cjs +3 -3
  27. package/dist/cjs/lazyRouteComponent.cjs.map +1 -1
  28. package/dist/cjs/link.cjs +40 -22
  29. package/dist/cjs/link.cjs.map +1 -1
  30. package/dist/cjs/not-found.cjs +1 -1
  31. package/dist/cjs/renderRouteNotFound.cjs +1 -1
  32. package/dist/cjs/route.cjs +1 -1
  33. package/dist/cjs/scroll-restoration.cjs +1 -1
  34. package/dist/cjs/ssr/RouterClient.cjs +4 -23
  35. package/dist/cjs/ssr/RouterClient.cjs.map +1 -1
  36. package/dist/cjs/ssr/RouterServer.cjs +4 -47
  37. package/dist/cjs/ssr/RouterServer.cjs.map +1 -1
  38. package/dist/cjs/ssr/RouterServer.d.cts +0 -1
  39. package/dist/cjs/ssr/defaultRenderHandler.cjs +1 -1
  40. package/dist/cjs/ssr/defaultStreamHandler.cjs +1 -1
  41. package/dist/cjs/ssr/renderRouterToStream.cjs +2 -3
  42. package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -1
  43. package/dist/cjs/ssr/renderRouterToString.cjs +2 -2
  44. package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -1
  45. package/dist/cjs/ssr/renderRouterToString.d.cts +1 -1
  46. package/dist/cjs/useBlocker.cjs +9 -5
  47. package/dist/cjs/useBlocker.cjs.map +1 -1
  48. package/dist/cjs/useMatch.cjs +3 -6
  49. package/dist/cjs/useMatch.cjs.map +1 -1
  50. package/dist/cjs/useNavigate.cjs +1 -1
  51. package/dist/cjs/useNavigate.cjs.map +1 -1
  52. package/dist/cjs/useRouterState.cjs +15 -9
  53. package/dist/cjs/useRouterState.cjs.map +1 -1
  54. package/dist/cjs/utils.cjs +2 -4
  55. package/dist/cjs/utils.cjs.map +1 -1
  56. package/dist/cjs/utils.d.cts +1 -0
  57. package/dist/esm/Asset.js +111 -68
  58. package/dist/esm/Asset.js.map +1 -1
  59. package/dist/esm/CatchBoundary.js +15 -15
  60. package/dist/esm/CatchBoundary.js.map +1 -1
  61. package/dist/esm/ClientOnly.js +2 -2
  62. package/dist/esm/ClientOnly.js.map +1 -1
  63. package/dist/esm/HeadContent.dev.js +18 -10
  64. package/dist/esm/HeadContent.dev.js.map +1 -1
  65. package/dist/esm/HeadContent.js +16 -8
  66. package/dist/esm/HeadContent.js.map +1 -1
  67. package/dist/esm/Match.js +89 -45
  68. package/dist/esm/Match.js.map +1 -1
  69. package/dist/esm/Matches.js +23 -16
  70. package/dist/esm/Matches.js.map +1 -1
  71. package/dist/esm/RouterProvider.js +3 -2
  72. package/dist/esm/RouterProvider.js.map +1 -1
  73. package/dist/esm/SafeFragment.js +1 -1
  74. package/dist/esm/ScriptOnce.js +4 -2
  75. package/dist/esm/ScriptOnce.js.map +1 -1
  76. package/dist/esm/Scripts.js +6 -2
  77. package/dist/esm/Scripts.js.map +1 -1
  78. package/dist/esm/Transitioner.js +11 -16
  79. package/dist/esm/Transitioner.js.map +1 -1
  80. package/dist/esm/awaited.js +18 -14
  81. package/dist/esm/awaited.js.map +1 -1
  82. package/dist/esm/lazyRouteComponent.js +3 -3
  83. package/dist/esm/lazyRouteComponent.js.map +1 -1
  84. package/dist/esm/link.js +39 -21
  85. package/dist/esm/link.js.map +1 -1
  86. package/dist/esm/not-found.js +1 -1
  87. package/dist/esm/renderRouteNotFound.js +1 -1
  88. package/dist/esm/route.js +1 -1
  89. package/dist/esm/scroll-restoration.js +1 -1
  90. package/dist/esm/ssr/RouterClient.js +4 -23
  91. package/dist/esm/ssr/RouterClient.js.map +1 -1
  92. package/dist/esm/ssr/RouterServer.d.ts +0 -1
  93. package/dist/esm/ssr/RouterServer.js +5 -48
  94. package/dist/esm/ssr/RouterServer.js.map +1 -1
  95. package/dist/esm/ssr/defaultRenderHandler.js +1 -1
  96. package/dist/esm/ssr/defaultStreamHandler.js +1 -1
  97. package/dist/esm/ssr/renderRouterToStream.js +2 -4
  98. package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
  99. package/dist/esm/ssr/renderRouterToString.d.ts +1 -1
  100. package/dist/esm/ssr/renderRouterToString.js +2 -2
  101. package/dist/esm/ssr/renderRouterToString.js.map +1 -1
  102. package/dist/esm/useBlocker.js +9 -5
  103. package/dist/esm/useBlocker.js.map +1 -1
  104. package/dist/esm/useMatch.js +3 -6
  105. package/dist/esm/useMatch.js.map +1 -1
  106. package/dist/esm/useNavigate.js +1 -1
  107. package/dist/esm/useNavigate.js.map +1 -1
  108. package/dist/esm/useRouterState.js +15 -9
  109. package/dist/esm/useRouterState.js.map +1 -1
  110. package/dist/esm/utils.d.ts +1 -0
  111. package/dist/esm/utils.js +2 -4
  112. package/dist/esm/utils.js.map +1 -1
  113. package/dist/source/Asset.jsx +58 -35
  114. package/dist/source/Asset.jsx.map +1 -1
  115. package/dist/source/CatchBoundary.jsx +9 -5
  116. package/dist/source/CatchBoundary.jsx.map +1 -1
  117. package/dist/source/ClientOnly.jsx +1 -1
  118. package/dist/source/ClientOnly.jsx.map +1 -1
  119. package/dist/source/HeadContent.dev.jsx +8 -6
  120. package/dist/source/HeadContent.dev.jsx.map +1 -1
  121. package/dist/source/HeadContent.jsx +6 -4
  122. package/dist/source/HeadContent.jsx.map +1 -1
  123. package/dist/source/Match.jsx +76 -35
  124. package/dist/source/Match.jsx.map +1 -1
  125. package/dist/source/Matches.jsx +25 -17
  126. package/dist/source/Matches.jsx.map +1 -1
  127. package/dist/source/RouterProvider.jsx +2 -3
  128. package/dist/source/RouterProvider.jsx.map +1 -1
  129. package/dist/source/Scripts.jsx +4 -3
  130. package/dist/source/Scripts.jsx.map +1 -1
  131. package/dist/source/Transitioner.jsx +15 -16
  132. package/dist/source/Transitioner.jsx.map +1 -1
  133. package/dist/source/awaited.jsx +7 -8
  134. package/dist/source/awaited.jsx.map +1 -1
  135. package/dist/source/lazyRouteComponent.jsx +3 -3
  136. package/dist/source/lazyRouteComponent.jsx.map +1 -1
  137. package/dist/source/link.jsx +53 -48
  138. package/dist/source/link.jsx.map +1 -1
  139. package/dist/source/ssr/RouterClient.jsx +1 -13
  140. package/dist/source/ssr/RouterClient.jsx.map +1 -1
  141. package/dist/source/ssr/RouterServer.d.ts +0 -1
  142. package/dist/source/ssr/RouterServer.jsx +1 -34
  143. package/dist/source/ssr/RouterServer.jsx.map +1 -1
  144. package/dist/source/ssr/renderRouterToStream.jsx +2 -6
  145. package/dist/source/ssr/renderRouterToStream.jsx.map +1 -1
  146. package/dist/source/ssr/renderRouterToString.d.ts +1 -1
  147. package/dist/source/ssr/renderRouterToString.jsx +2 -2
  148. package/dist/source/ssr/renderRouterToString.jsx.map +1 -1
  149. package/dist/source/useBlocker.jsx +8 -4
  150. package/dist/source/useBlocker.jsx.map +1 -1
  151. package/dist/source/useMatch.jsx +3 -8
  152. package/dist/source/useMatch.jsx.map +1 -1
  153. package/dist/source/useNavigate.jsx +1 -1
  154. package/dist/source/useNavigate.jsx.map +1 -1
  155. package/dist/source/useRouterState.jsx +23 -10
  156. package/dist/source/useRouterState.jsx.map +1 -1
  157. package/dist/source/utils.d.ts +1 -0
  158. package/dist/source/utils.js +3 -4
  159. package/dist/source/utils.js.map +1 -1
  160. package/package.json +7 -6
  161. package/src/Asset.tsx +123 -95
  162. package/src/CatchBoundary.tsx +9 -7
  163. package/src/ClientOnly.tsx +8 -3
  164. package/src/HeadContent.dev.tsx +16 -11
  165. package/src/HeadContent.tsx +6 -4
  166. package/src/Match.tsx +112 -44
  167. package/src/Matches.tsx +39 -30
  168. package/src/RouterProvider.tsx +7 -4
  169. package/src/Scripts.tsx +4 -3
  170. package/src/Transitioner.tsx +51 -58
  171. package/src/awaited.tsx +11 -12
  172. package/src/lazyRouteComponent.tsx +3 -3
  173. package/src/link.tsx +68 -60
  174. package/src/ssr/RouterClient.tsx +1 -22
  175. package/src/ssr/RouterServer.tsx +1 -53
  176. package/src/ssr/renderRouterToStream.tsx +5 -15
  177. package/src/ssr/renderRouterToString.tsx +2 -2
  178. package/src/useBlocker.tsx +8 -4
  179. package/src/useMatch.tsx +6 -11
  180. package/src/useNavigate.tsx +1 -1
  181. package/src/useRouterState.tsx +34 -22
  182. package/src/utils.ts +5 -4
package/src/Match.tsx CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  rootRouteId,
10
10
  } from '@tanstack/router-core'
11
11
  import { isServer } from '@tanstack/router-core/isServer'
12
- import { Dynamic } from 'solid-js/web'
12
+ import { Dynamic } from '@solidjs/web'
13
13
  import { CatchBoundary, ErrorComponent } from './CatchBoundary'
14
14
  import { useRouterState } from './useRouterState'
15
15
  import { useRouter } from './useRouter'
@@ -20,6 +20,11 @@ import { renderRouteNotFound } from './renderRouteNotFound'
20
20
  import { ScrollRestoration } from './scroll-restoration'
21
21
  import type { AnyRoute, RootRouteOptions } from '@tanstack/router-core'
22
22
 
23
+ const MatchContext = matchContext as unknown as Solid.Component<{
24
+ value: any
25
+ children?: any
26
+ }>
27
+
23
28
  export const Match = (props: { matchId: string }) => {
24
29
  const router = useRouter()
25
30
  const matchState = useRouterState({
@@ -41,7 +46,7 @@ export const Match = (props: { matchId: string }) => {
41
46
  })
42
47
 
43
48
  // If match doesn't exist yet, return null (component is being unmounted or not ready)
44
- if (!matchState()) return null
49
+ if (!Solid.untrack(matchState)) return null
45
50
 
46
51
  const route: () => AnyRoute = () => router.routesById[matchState()!.routeId]
47
52
 
@@ -61,10 +66,12 @@ export const Match = (props: { matchId: string }) => {
61
66
  router.options.notFoundRoute?.options.component)
62
67
  : route().options.notFoundComponent
63
68
 
64
- const resolvedNoSsr =
65
- matchState()!.ssr === false || matchState()!.ssr === 'data-only'
69
+ const resolvedNoSsr = Solid.createMemo(
70
+ () => matchState()!.ssr === false || matchState()!.ssr === 'data-only',
71
+ )
66
72
 
67
- const ResolvedSuspenseBoundary = () => Solid.Suspense
73
+ const ResolvedSuspenseBoundary = () =>
74
+ resolvedNoSsr() ? SafeFragment : Solid.Loading
68
75
 
69
76
  const ResolvedCatchBoundary = () =>
70
77
  routeErrorComponent() ? CatchBoundary : SafeFragment
@@ -83,18 +90,20 @@ export const Match = (props: { matchId: string }) => {
83
90
  },
84
91
  })
85
92
 
86
- const ShellComponent = route().isRoot
87
- ? ((route().options as RootRouteOptions).shellComponent ?? SafeFragment)
88
- : SafeFragment
93
+ const ShellComponent = Solid.createMemo(() =>
94
+ route().isRoot
95
+ ? ((route().options as RootRouteOptions).shellComponent ?? SafeFragment)
96
+ : SafeFragment,
97
+ )
89
98
 
90
99
  return (
91
- <ShellComponent>
92
- <matchContext.Provider value={() => props.matchId}>
100
+ <Dynamic component={ShellComponent()}>
101
+ <MatchContext value={() => props.matchId}>
93
102
  <Dynamic
94
103
  component={ResolvedSuspenseBoundary()}
95
104
  fallback={
96
105
  // Don't show fallback on server when using no-ssr mode to avoid hydration mismatch
97
- (isServer ?? router.isServer) || resolvedNoSsr ? undefined : (
106
+ (isServer ?? router.isServer) || resolvedNoSsr() ? undefined : (
98
107
  <Dynamic component={resolvePendingComponent()} />
99
108
  )
100
109
  }
@@ -106,7 +115,10 @@ export const Match = (props: { matchId: string }) => {
106
115
  onCatch={(error: Error) => {
107
116
  // Forward not found errors (we don't want to show the error component for these)
108
117
  if (isNotFound(error)) throw error
109
- warning(false, `Error in route match: ${matchState()!.routeId}`)
118
+ warning(
119
+ false,
120
+ `Error in route match: ${Solid.untrack(matchState)!.routeId}`,
121
+ )
110
122
  routeOnCatch()?.(error)
111
123
  }}
112
124
  >
@@ -128,7 +140,7 @@ export const Match = (props: { matchId: string }) => {
128
140
  }}
129
141
  >
130
142
  <Solid.Switch>
131
- <Solid.Match when={resolvedNoSsr}>
143
+ <Solid.Match when={resolvedNoSsr()}>
132
144
  <Solid.Show
133
145
  when={!(isServer ?? router.isServer)}
134
146
  fallback={<Dynamic component={resolvePendingComponent()} />}
@@ -136,14 +148,14 @@ export const Match = (props: { matchId: string }) => {
136
148
  <MatchInner matchId={props.matchId} />
137
149
  </Solid.Show>
138
150
  </Solid.Match>
139
- <Solid.Match when={!resolvedNoSsr}>
151
+ <Solid.Match when={!resolvedNoSsr()}>
140
152
  <MatchInner matchId={props.matchId} />
141
153
  </Solid.Match>
142
154
  </Solid.Switch>
143
155
  </Dynamic>
144
156
  </Dynamic>
145
157
  </Dynamic>
146
- </matchContext.Provider>
158
+ </MatchContext>
147
159
 
148
160
  {parentRouteId() === rootRouteId ? (
149
161
  <>
@@ -151,7 +163,7 @@ export const Match = (props: { matchId: string }) => {
151
163
  <ScrollRestoration />
152
164
  </>
153
165
  ) : null}
154
- </ShellComponent>
166
+ </Dynamic>
155
167
  )
156
168
  }
157
169
 
@@ -162,6 +174,14 @@ export const Match = (props: { matchId: string }) => {
162
174
  // allows us to fire onRendered events even after a hydration mismatch
163
175
  // error that occurred above the root layout (like bad head/link tags,
164
176
  // which is common).
177
+ //
178
+ // In Solid, createEffect(source, fn) fires on initial mount as well as on
179
+ // reactive changes. OnRendered can also remount when the first child route
180
+ // changes (e.g. navigating from / to /posts). We deduplicate by tracking
181
+ // the last emitted resolvedLocation key per router so each unique resolved
182
+ // location only triggers one onRendered event regardless of remounts.
183
+ const lastOnRenderedKey = new WeakMap<object, string>()
184
+
165
185
  function OnRendered() {
166
186
  const router = useRouter()
167
187
 
@@ -171,12 +191,16 @@ function OnRendered() {
171
191
  },
172
192
  })
173
193
  Solid.createEffect(
174
- Solid.on([location], () => {
194
+ () => [location()] as const,
195
+ ([location]) => {
196
+ if (!location) return
197
+ if (lastOnRenderedKey.get(router) === location) return
198
+ lastOnRenderedKey.set(router, location)
175
199
  router.emit({
176
200
  type: 'onRendered',
177
201
  ...getLocationChangeInfo(router.state),
178
202
  })
179
- }),
203
+ },
180
204
  )
181
205
  return null
182
206
  }
@@ -220,7 +244,7 @@ export const MatchInner = (props: { matchId: string }): any => {
220
244
  },
221
245
  })
222
246
 
223
- if (!matchState()) return null
247
+ if (!Solid.untrack(matchState)) return null
224
248
 
225
249
  const route = () => router.routesById[matchState()!.routeId]!
226
250
 
@@ -229,7 +253,9 @@ export const MatchInner = (props: { matchId: string }): any => {
229
253
  const componentKey = () => matchState()!.key ?? matchState()!.match.id
230
254
 
231
255
  const out = () => {
232
- const Comp = route().options.component ?? router.options.defaultComponent
256
+ const currentRoute = Solid.untrack(route)
257
+ const Comp =
258
+ currentRoute.options.component ?? router.options.defaultComponent
233
259
  if (Comp) {
234
260
  return <Comp />
235
261
  }
@@ -246,9 +272,9 @@ export const MatchInner = (props: { matchId: string }): any => {
246
272
  <Solid.Switch>
247
273
  <Solid.Match when={match()._displayPending}>
248
274
  {(_) => {
249
- const [displayPendingResult] = Solid.createResource(
250
- () =>
251
- router.getMatch(match().id)?._nonReactive.displayPendingPromise,
275
+ const matchId = Solid.untrack(() => match().id)
276
+ const displayPendingResult = Solid.createMemo(
277
+ () => router.getMatch(matchId)?._nonReactive.displayPendingPromise,
252
278
  )
253
279
 
254
280
  return <>{displayPendingResult()}</>
@@ -256,8 +282,9 @@ export const MatchInner = (props: { matchId: string }): any => {
256
282
  </Solid.Match>
257
283
  <Solid.Match when={match()._forcePending}>
258
284
  {(_) => {
259
- const [minPendingResult] = Solid.createResource(
260
- () => router.getMatch(match().id)?._nonReactive.minPendingPromise,
285
+ const matchId = Solid.untrack(() => match().id)
286
+ const minPendingResult = Solid.createMemo(
287
+ () => router.getMatch(matchId)?._nonReactive.minPendingPromise,
261
288
  )
262
289
 
263
290
  return <>{minPendingResult()}</>
@@ -265,11 +292,14 @@ export const MatchInner = (props: { matchId: string }): any => {
265
292
  </Solid.Match>
266
293
  <Solid.Match when={match().status === 'pending'}>
267
294
  {(_) => {
295
+ const currentMatch = Solid.untrack(match)
296
+ const currentRoute = Solid.untrack(route)
268
297
  const pendingMinMs =
269
- route().options.pendingMinMs ?? router.options.defaultPendingMinMs
298
+ currentRoute.options.pendingMinMs ??
299
+ router.options.defaultPendingMinMs
270
300
 
271
301
  if (pendingMinMs) {
272
- const routerMatch = router.getMatch(match().id)
302
+ const routerMatch = router.getMatch(currentMatch.id)
273
303
  if (routerMatch && !routerMatch._nonReactive.minPendingPromise) {
274
304
  // Create a promise that will resolve after the minPendingMs
275
305
  if (!(isServer ?? router.isServer)) {
@@ -286,13 +316,13 @@ export const MatchInner = (props: { matchId: string }): any => {
286
316
  }
287
317
  }
288
318
 
289
- const [loaderResult] = Solid.createResource(async () => {
319
+ const loaderResult = Solid.createMemo(async () => {
290
320
  await new Promise((r) => setTimeout(r, 0))
291
- return router.getMatch(match().id)?._nonReactive.loadPromise
321
+ return router.getMatch(currentMatch.id)?._nonReactive.loadPromise
292
322
  })
293
323
 
294
324
  const FallbackComponent =
295
- route().options.pendingComponent ??
325
+ currentRoute.options.pendingComponent ??
296
326
  router.options.defaultPendingComponent
297
327
 
298
328
  return (
@@ -307,13 +337,16 @@ export const MatchInner = (props: { matchId: string }): any => {
307
337
  </Solid.Match>
308
338
  <Solid.Match when={match().status === 'notFound'}>
309
339
  {(_) => {
310
- invariant(isNotFound(match().error), 'Expected a notFound error')
340
+ const currentMatch = Solid.untrack(match)
341
+ const currentRoute = Solid.untrack(route)
342
+ const currentRouteId = Solid.untrack(() => matchState()!.routeId)
343
+ invariant(isNotFound(currentMatch.error), 'Expected a notFound error')
311
344
 
312
345
  // Use Show with keyed to ensure re-render when routeId changes
313
346
  return (
314
- <Solid.Show when={matchState()!.routeId} keyed>
347
+ <Solid.Show when={currentRouteId} keyed>
315
348
  {(_routeId) =>
316
- renderRouteNotFound(router, route(), match().error)
349
+ renderRouteNotFound(router, currentRoute, currentMatch.error)
317
350
  }
318
351
  </Solid.Show>
319
352
  )
@@ -321,11 +354,15 @@ export const MatchInner = (props: { matchId: string }): any => {
321
354
  </Solid.Match>
322
355
  <Solid.Match when={match().status === 'redirected'}>
323
356
  {(_) => {
324
- invariant(isRedirect(match().error), 'Expected a redirect error')
357
+ const matchId = Solid.untrack(() => match().id)
358
+ invariant(
359
+ isRedirect(Solid.untrack(match).error),
360
+ 'Expected a redirect error',
361
+ )
325
362
 
326
- const [loaderResult] = Solid.createResource(async () => {
363
+ const loaderResult = Solid.createMemo(async () => {
327
364
  await new Promise((r) => setTimeout(r, 0))
328
- return router.getMatch(match().id)?._nonReactive.loadPromise
365
+ return router.getMatch(matchId)?._nonReactive.loadPromise
329
366
  })
330
367
 
331
368
  return <>{loaderResult()}</>
@@ -333,7 +370,24 @@ export const MatchInner = (props: { matchId: string }): any => {
333
370
  </Solid.Match>
334
371
  <Solid.Match when={match().status === 'error'}>
335
372
  {(_) => {
336
- throw match().error
373
+ if (isServer ?? router.isServer) {
374
+ const currentMatch = Solid.untrack(match)
375
+ const RouteErrorComponent =
376
+ (Solid.untrack(route).options.errorComponent ??
377
+ router.options.defaultErrorComponent) ||
378
+ ErrorComponent
379
+
380
+ return (
381
+ <RouteErrorComponent
382
+ error={currentMatch.error}
383
+ info={{
384
+ componentStack: '',
385
+ }}
386
+ />
387
+ )
388
+ }
389
+
390
+ throw Solid.untrack(match).error
337
391
  }}
338
392
  </Solid.Match>
339
393
  <Solid.Match when={match().status === 'success'}>
@@ -376,6 +430,14 @@ export const Outlet = () => {
376
430
  },
377
431
  })
378
432
 
433
+ const childRouteId = useRouterState({
434
+ select: (s) => {
435
+ const matches = s.matches
436
+ const index = matches.findIndex((d) => d.id === matchId())
437
+ return matches[index + 1]?.routeId
438
+ },
439
+ })
440
+
379
441
  const childMatchStatus = useRouterState({
380
442
  select: (s) => {
381
443
  const matches = s.matches
@@ -406,13 +468,19 @@ export const Outlet = () => {
406
468
  when={routeId() === rootRouteId}
407
469
  fallback={<Match matchId={currentMatchId()} />}
408
470
  >
409
- <Solid.Suspense
410
- fallback={
411
- <Dynamic component={router.options.defaultPendingComponent} />
412
- }
413
- >
414
- <Match matchId={currentMatchId()} />
415
- </Solid.Suspense>
471
+ <Solid.Show when={childRouteId()} keyed>
472
+ {(_routeId) => (
473
+ <Solid.Loading
474
+ fallback={
475
+ <Dynamic
476
+ component={router.options.defaultPendingComponent}
477
+ />
478
+ }
479
+ >
480
+ <Match matchId={currentMatchId()} />
481
+ </Solid.Loading>
482
+ )}
483
+ </Solid.Show>
416
484
  </Solid.Show>
417
485
  )
418
486
  }}
package/src/Matches.tsx CHANGED
@@ -28,6 +28,11 @@ import type {
28
28
  ToSubOptionsProps,
29
29
  } from '@tanstack/router-core'
30
30
 
31
+ const MatchContext = matchContext as unknown as Solid.Component<{
32
+ value: any
33
+ children: any
34
+ }>
35
+
31
36
  declare module '@tanstack/router-core' {
32
37
  export interface RouteMatchExtensions {
33
38
  meta?: Array<Solid.JSX.IntrinsicElements['meta'] | undefined>
@@ -41,11 +46,15 @@ declare module '@tanstack/router-core' {
41
46
  export function Matches() {
42
47
  const router = useRouter()
43
48
 
49
+ // When disableGlobalCatchBoundary is true, we must NOT wrap with Solid.Loading
50
+ // because Solid.Loading transforms STATUS_ERROR into STATUS_PENDING, which
51
+ // prevents errors from propagating to an external Errored boundary.
44
52
  const ResolvedSuspense =
53
+ router.options.disableGlobalCatchBoundary ||
45
54
  (isServer ?? router.isServer) ||
46
55
  (typeof document !== 'undefined' && router.ssr)
47
56
  ? SafeFragment
48
- : Solid.Suspense
57
+ : Solid.Loading
49
58
 
50
59
  const rootRoute: () => AnyRoute = () => router.routesById[rootRouteId]
51
60
  const PendingComponent =
@@ -78,39 +87,39 @@ function MatchesInner() {
78
87
  select: (s) => s.loadedAt,
79
88
  })
80
89
 
81
- const matchComponent = () => {
82
- return (
83
- <Solid.Show when={matchId()}>
84
- <Match matchId={matchId()!} />
85
- </Solid.Show>
86
- )
90
+ const matchContent = () => (
91
+ <Solid.Show when={matchId()}>
92
+ <Match matchId={matchId()!} />
93
+ </Solid.Show>
94
+ )
95
+
96
+ if (router.options.disableGlobalCatchBoundary) {
97
+ // When disableGlobalCatchBoundary is true, render without any internal
98
+ // error boundary so errors bubble up freely to an external Errored boundary.
99
+ return <MatchContext value={matchId}>{matchContent()}</MatchContext>
87
100
  }
88
101
 
89
102
  return (
90
- <matchContext.Provider value={matchId}>
91
- {router.options.disableGlobalCatchBoundary ? (
92
- matchComponent()
93
- ) : (
94
- <CatchBoundary
95
- getResetKey={() => resetKey()}
96
- errorComponent={ErrorComponent}
97
- onCatch={
98
- process.env.NODE_ENV !== 'production'
99
- ? (error) => {
100
- warning(
101
- false,
102
- `The following error wasn't caught by any route! At the very leas
103
+ <MatchContext value={matchId}>
104
+ <CatchBoundary
105
+ getResetKey={() => resetKey()}
106
+ errorComponent={ErrorComponent}
107
+ onCatch={
108
+ process.env.NODE_ENV !== 'production'
109
+ ? (error) => {
110
+ warning(
111
+ false,
112
+ `The following error wasn't caught by any route! At the very leas
103
113
  t, consider setting an 'errorComponent' in your RootRoute!`,
104
- )
105
- warning(false, error.message || error.toString())
106
- }
107
- : undefined
108
- }
109
- >
110
- {matchComponent()}
111
- </CatchBoundary>
112
- )}
113
- </matchContext.Provider>
114
+ )
115
+ warning(false, error.message || error.toString())
116
+ }
117
+ : undefined
118
+ }
119
+ >
120
+ {matchContent()}
121
+ </CatchBoundary>
122
+ </MatchContext>
114
123
  )
115
124
  }
116
125
 
@@ -8,6 +8,11 @@ import type {
8
8
  } from '@tanstack/router-core'
9
9
  import type * as Solid from 'solid-js'
10
10
 
11
+ const RouterContext = routerContext as unknown as Solid.Component<{
12
+ value: any
13
+ children: any
14
+ }>
15
+
11
16
  export function RouterContextProvider<
12
17
  TRouter extends AnyRouter = RegisteredRouter,
13
18
  TDehydrated extends Record<string, any> = Record<string, any>,
@@ -26,15 +31,13 @@ export function RouterContextProvider<
26
31
  ...router.options.context,
27
32
  ...rest.context,
28
33
  },
29
- })
34
+ } as any)
30
35
 
31
36
  const OptionalWrapper = router.options.Wrap || SafeFragment
32
37
 
33
38
  return (
34
39
  <OptionalWrapper>
35
- <routerContext.Provider value={router as AnyRouter}>
36
- {children()}
37
- </routerContext.Provider>
40
+ <RouterContext value={router as AnyRouter}>{children()}</RouterContext>
38
41
  </OptionalWrapper>
39
42
  )
40
43
  }
package/src/Scripts.tsx CHANGED
@@ -1,3 +1,4 @@
1
+ import { NoHydration } from '@solidjs/web'
1
2
  import { Asset } from './Asset'
2
3
  import { useRouterState } from './useRouterState'
3
4
  import { useRouter } from './useRouter'
@@ -67,10 +68,10 @@ export const Scripts = () => {
67
68
  }
68
69
 
69
70
  return (
70
- <>
71
- {allScripts.map((asset, i) => (
71
+ <NoHydration>
72
+ {allScripts.map((asset, _i) => (
72
73
  <Asset {...asset} />
73
74
  ))}
74
- </>
75
+ </NoHydration>
75
76
  )
76
77
  }
@@ -4,7 +4,6 @@ import {
4
4
  handleHashScroll,
5
5
  trimPathRight,
6
6
  } from '@tanstack/router-core'
7
- import { isServer } from '@tanstack/router-core/isServer'
8
7
  import { useRouter } from './useRouter'
9
8
  import { useRouterState } from './useRouterState'
10
9
  import { usePrevious } from './utils'
@@ -16,11 +15,7 @@ export function Transitioner() {
16
15
  select: ({ isLoading }) => isLoading,
17
16
  })
18
17
 
19
- if (isServer ?? router.isServer) {
20
- return null
21
- }
22
-
23
- const [isSolidTransitioning, startSolidTransition] = Solid.useTransition()
18
+ const [isSolidTransitioning] = [() => false]
24
19
 
25
20
  // Track pending state changes
26
21
  const hasPendingMatches = useRouterState({
@@ -37,16 +32,20 @@ export function Transitioner() {
37
32
  const previousIsPagePending = usePrevious(isPagePending)
38
33
 
39
34
  router.startTransition = (fn: () => void | Promise<void>) => {
40
- Solid.startTransition(() => {
41
- startSolidTransition(fn)
42
- })
35
+ Solid.runWithOwner(null, fn)
43
36
  }
44
37
 
45
38
  // Subscribe to location changes
46
39
  // and try to load the new location
47
- Solid.onMount(() => {
40
+ Solid.onSettled(() => {
48
41
  const unsub = router.history.subscribe(router.load)
49
42
 
43
+ // Refresh latestLocation from the current browser URL before comparing.
44
+ // The URL may have been changed synchronously (e.g. via replaceState) after
45
+ // render() but before this effect ran, so we must not use the stale
46
+ // render-time location here.
47
+ router.updateLatestLocation()
48
+
50
49
  const nextLocation = router.buildLocation({
51
50
  to: router.latestLocation.pathname,
52
51
  search: true,
@@ -72,7 +71,7 @@ export function Transitioner() {
72
71
  })
73
72
 
74
73
  // Try to load the initial location
75
- Solid.createRenderEffect(() => {
74
+ Solid.createTrackedEffect(() => {
76
75
  Solid.untrack(() => {
77
76
  if (
78
77
  // if we are hydrating from SSR, loading is triggered in ssr-client
@@ -93,58 +92,52 @@ export function Transitioner() {
93
92
  })
94
93
  })
95
94
 
96
- Solid.createRenderEffect(
97
- Solid.on(
98
- [previousIsLoading, isLoading],
99
- ([previousIsLoading, isLoading]) => {
100
- if (previousIsLoading.previous && !isLoading) {
101
- router.emit({
102
- type: 'onLoad',
103
- ...getLocationChangeInfo(router.state),
104
- })
105
- }
106
- },
107
- ),
95
+ Solid.createEffect(
96
+ () => [previousIsLoading(), isLoading()] as const,
97
+ ([previousIsLoading, isLoading]) => {
98
+ if (previousIsLoading.previous && !isLoading) {
99
+ router.emit({
100
+ type: 'onLoad',
101
+ ...getLocationChangeInfo(router.state),
102
+ })
103
+ }
104
+ },
108
105
  )
109
106
 
110
- Solid.createComputed(
111
- Solid.on(
112
- [isPagePending, previousIsPagePending],
113
- ([isPagePending, previousIsPagePending]) => {
114
- // emit onBeforeRouteMount
115
- if (previousIsPagePending.previous && !isPagePending) {
116
- router.emit({
117
- type: 'onBeforeRouteMount',
118
- ...getLocationChangeInfo(router.state),
119
- })
120
- }
121
- },
122
- ),
107
+ Solid.createEffect(
108
+ () => [isPagePending(), previousIsPagePending()] as const,
109
+ ([isPagePending, previousIsPagePending]) => {
110
+ // emit onBeforeRouteMount
111
+ if (previousIsPagePending.previous && !isPagePending) {
112
+ router.emit({
113
+ type: 'onBeforeRouteMount',
114
+ ...getLocationChangeInfo(router.state),
115
+ })
116
+ }
117
+ },
123
118
  )
124
119
 
125
- Solid.createRenderEffect(
126
- Solid.on(
127
- [isAnyPending, previousIsAnyPending],
128
- ([isAnyPending, previousIsAnyPending]) => {
129
- if (previousIsAnyPending.previous && !isAnyPending) {
130
- const changeInfo = getLocationChangeInfo(router.state)
131
- router.emit({
132
- type: 'onResolved',
133
- ...changeInfo,
134
- })
135
-
136
- router.__store.setState((s) => ({
137
- ...s,
138
- status: 'idle',
139
- resolvedLocation: s.location,
140
- }))
141
-
142
- if (changeInfo.hrefChanged) {
143
- handleHashScroll(router)
144
- }
120
+ Solid.createEffect(
121
+ () => [isAnyPending(), previousIsAnyPending()] as const,
122
+ ([isAnyPending, previousIsAnyPending]) => {
123
+ if (previousIsAnyPending.previous && !isAnyPending) {
124
+ const changeInfo = getLocationChangeInfo(router.state)
125
+ router.emit({
126
+ type: 'onResolved',
127
+ ...changeInfo,
128
+ })
129
+
130
+ router.__store.setState((s) => ({
131
+ ...s,
132
+ status: 'idle',
133
+ resolvedLocation: s.location,
134
+ }))
135
+
136
+ if (changeInfo.hrefChanged) {
137
+ handleHashScroll(router)
145
138
  }
146
- },
147
- ),
139
+ }
140
+ },
148
141
  )
149
142
 
150
143
  return null
package/src/awaited.tsx CHANGED
@@ -24,24 +24,23 @@ export function useAwaited<T>({
24
24
  return [promise[TSR_DEFERRED_PROMISE].data, promise]
25
25
  }
26
26
 
27
+ function InnerAwait<T>(props: {
28
+ promise: Promise<T>
29
+ children: (res: T) => SolidNode
30
+ }) {
31
+ const [data] = useAwaited({ promise: props.promise })
32
+ return props.children(data) as any
33
+ }
34
+
27
35
  export function Await<T>(
28
36
  props: AwaitOptions<T> & {
29
37
  fallback?: SolidNode
30
38
  children: (result: T) => SolidNode
31
39
  },
32
40
  ) {
33
- const [resource] = Solid.createResource(
34
- () => defer(props.promise),
35
- // Simple passthrough - just return the promise for Solid to await
36
- (p) => p,
37
- {
38
- deferStream: true,
39
- },
40
- )
41
-
42
41
  return (
43
- <Solid.Show fallback={props.fallback} when={resource()}>
44
- {(data) => props.children(data())}
45
- </Solid.Show>
42
+ <Solid.Loading fallback={props.fallback as any}>
43
+ <InnerAwait promise={props.promise}>{props.children}</InnerAwait>
44
+ </Solid.Loading>
46
45
  )
47
46
  }