@tanstack/solid-router 2.0.0-alpha.6 → 2.0.0-alpha.7

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 (219) hide show
  1. package/dist/cjs/Asset.cjs +2 -2
  2. package/dist/cjs/Asset.cjs.map +1 -1
  3. package/dist/cjs/HeadContent.cjs +11 -1
  4. package/dist/cjs/HeadContent.cjs.map +1 -1
  5. package/dist/cjs/HeadContent.dev.cjs +11 -1
  6. package/dist/cjs/HeadContent.dev.cjs.map +1 -1
  7. package/dist/cjs/Match.cjs +265 -248
  8. package/dist/cjs/Match.cjs.map +1 -1
  9. package/dist/cjs/Match.d.cts +1 -3
  10. package/dist/cjs/Matches.cjs +35 -34
  11. package/dist/cjs/Matches.cjs.map +1 -1
  12. package/dist/cjs/RouterProvider.cjs +12 -8
  13. package/dist/cjs/RouterProvider.cjs.map +1 -1
  14. package/dist/cjs/RouterProvider.d.cts +1 -1
  15. package/dist/cjs/Scripts.cjs +23 -12
  16. package/dist/cjs/Scripts.cjs.map +1 -1
  17. package/dist/cjs/Scripts.d.cts +2 -1
  18. package/dist/cjs/Transitioner.cjs +55 -34
  19. package/dist/cjs/Transitioner.cjs.map +1 -1
  20. package/dist/cjs/headContentUtils.cjs +26 -23
  21. package/dist/cjs/headContentUtils.cjs.map +1 -1
  22. package/dist/cjs/headContentUtils.d.cts +2 -1
  23. package/dist/cjs/index.cjs +1 -1
  24. package/dist/cjs/index.dev.cjs +1 -1
  25. package/dist/cjs/link.cjs +143 -101
  26. package/dist/cjs/link.cjs.map +1 -1
  27. package/dist/cjs/matchContext.cjs +7 -5
  28. package/dist/cjs/matchContext.cjs.map +1 -1
  29. package/dist/cjs/matchContext.d.cts +8 -2
  30. package/dist/cjs/not-found.cjs +8 -4
  31. package/dist/cjs/not-found.cjs.map +1 -1
  32. package/dist/cjs/not-found.d.cts +1 -1
  33. package/dist/cjs/router.cjs +2 -1
  34. package/dist/cjs/router.cjs.map +1 -1
  35. package/dist/cjs/routerStores.cjs +75 -0
  36. package/dist/cjs/routerStores.cjs.map +1 -0
  37. package/dist/cjs/routerStores.d.cts +10 -0
  38. package/dist/cjs/ssr/RouterClient.cjs +1 -1
  39. package/dist/cjs/ssr/RouterClient.cjs.map +1 -1
  40. package/dist/cjs/ssr/renderRouterToStream.cjs +1 -1
  41. package/dist/cjs/ssr/renderRouterToStream.cjs.map +1 -1
  42. package/dist/cjs/ssr/renderRouterToString.cjs +1 -1
  43. package/dist/cjs/ssr/renderRouterToString.cjs.map +1 -1
  44. package/dist/cjs/useBlocker.cjs +12 -3
  45. package/dist/cjs/useBlocker.cjs.map +1 -1
  46. package/dist/cjs/useCanGoBack.cjs +6 -2
  47. package/dist/cjs/useCanGoBack.cjs.map +1 -1
  48. package/dist/cjs/useCanGoBack.d.cts +2 -1
  49. package/dist/cjs/useLoaderDeps.cjs +2 -3
  50. package/dist/cjs/useLoaderDeps.cjs.map +1 -1
  51. package/dist/cjs/useLocation.cjs +13 -2
  52. package/dist/cjs/useLocation.cjs.map +1 -1
  53. package/dist/cjs/useMatch.cjs +27 -15
  54. package/dist/cjs/useMatch.cjs.map +1 -1
  55. package/dist/cjs/useParams.cjs +1 -1
  56. package/dist/cjs/useParams.cjs.map +1 -1
  57. package/dist/cjs/useRouterState.cjs +12 -30
  58. package/dist/cjs/useRouterState.cjs.map +1 -1
  59. package/dist/cjs/useSearch.cjs +2 -1
  60. package/dist/cjs/useSearch.cjs.map +1 -1
  61. package/dist/cjs/utils.cjs +3 -17
  62. package/dist/cjs/utils.cjs.map +1 -1
  63. package/dist/cjs/utils.d.cts +0 -5
  64. package/dist/esm/Asset.js +6 -6
  65. package/dist/esm/Asset.js.map +1 -1
  66. package/dist/esm/HeadContent.dev.js +12 -2
  67. package/dist/esm/HeadContent.dev.js.map +1 -1
  68. package/dist/esm/HeadContent.js +12 -2
  69. package/dist/esm/HeadContent.js.map +1 -1
  70. package/dist/esm/Match.d.ts +1 -3
  71. package/dist/esm/Match.js +267 -250
  72. package/dist/esm/Match.js.map +1 -1
  73. package/dist/esm/Matches.js +40 -39
  74. package/dist/esm/Matches.js.map +1 -1
  75. package/dist/esm/RouterProvider.d.ts +1 -1
  76. package/dist/esm/RouterProvider.js +10 -7
  77. package/dist/esm/RouterProvider.js.map +1 -1
  78. package/dist/esm/ScriptOnce.js +2 -2
  79. package/dist/esm/ScriptOnce.js.map +1 -1
  80. package/dist/esm/Scripts.d.ts +2 -1
  81. package/dist/esm/Scripts.js +21 -11
  82. package/dist/esm/Scripts.js.map +1 -1
  83. package/dist/esm/Transitioner.js +56 -35
  84. package/dist/esm/Transitioner.js.map +1 -1
  85. package/dist/esm/headContentUtils.d.ts +2 -1
  86. package/dist/esm/headContentUtils.js +26 -23
  87. package/dist/esm/headContentUtils.js.map +1 -1
  88. package/dist/esm/index.dev.js +1 -1
  89. package/dist/esm/index.js +1 -1
  90. package/dist/esm/link.js +146 -104
  91. package/dist/esm/link.js.map +1 -1
  92. package/dist/esm/matchContext.d.ts +8 -2
  93. package/dist/esm/matchContext.js +7 -4
  94. package/dist/esm/matchContext.js.map +1 -1
  95. package/dist/esm/not-found.d.ts +1 -1
  96. package/dist/esm/not-found.js +6 -3
  97. package/dist/esm/not-found.js.map +1 -1
  98. package/dist/esm/router.js +2 -1
  99. package/dist/esm/router.js.map +1 -1
  100. package/dist/esm/routerStores.d.ts +10 -0
  101. package/dist/esm/routerStores.js +73 -0
  102. package/dist/esm/routerStores.js.map +1 -0
  103. package/dist/esm/scroll-restoration.js +2 -2
  104. package/dist/esm/scroll-restoration.js.map +1 -1
  105. package/dist/esm/ssr/RouterClient.js +1 -1
  106. package/dist/esm/ssr/RouterClient.js.map +1 -1
  107. package/dist/esm/ssr/renderRouterToStream.js +1 -1
  108. package/dist/esm/ssr/renderRouterToStream.js.map +1 -1
  109. package/dist/esm/ssr/renderRouterToString.js +1 -1
  110. package/dist/esm/ssr/renderRouterToString.js.map +1 -1
  111. package/dist/esm/useBlocker.js +12 -3
  112. package/dist/esm/useBlocker.js.map +1 -1
  113. package/dist/esm/useCanGoBack.d.ts +2 -1
  114. package/dist/esm/useCanGoBack.js +4 -2
  115. package/dist/esm/useCanGoBack.js.map +1 -1
  116. package/dist/esm/useLoaderDeps.js +2 -3
  117. package/dist/esm/useLoaderDeps.js.map +1 -1
  118. package/dist/esm/useLocation.js +11 -2
  119. package/dist/esm/useLocation.js.map +1 -1
  120. package/dist/esm/useMatch.js +28 -16
  121. package/dist/esm/useMatch.js.map +1 -1
  122. package/dist/esm/useParams.js +1 -1
  123. package/dist/esm/useParams.js.map +1 -1
  124. package/dist/esm/useRouterState.js +11 -30
  125. package/dist/esm/useRouterState.js.map +1 -1
  126. package/dist/esm/useSearch.js +2 -1
  127. package/dist/esm/useSearch.js.map +1 -1
  128. package/dist/esm/utils.d.ts +0 -5
  129. package/dist/esm/utils.js +4 -17
  130. package/dist/esm/utils.js.map +1 -1
  131. package/dist/source/Asset.jsx +3 -3
  132. package/dist/source/Asset.jsx.map +1 -1
  133. package/dist/source/HeadContent.dev.jsx +5 -1
  134. package/dist/source/HeadContent.dev.jsx.map +1 -1
  135. package/dist/source/HeadContent.jsx +5 -1
  136. package/dist/source/HeadContent.jsx.map +1 -1
  137. package/dist/source/Match.d.ts +1 -3
  138. package/dist/source/Match.jsx +260 -264
  139. package/dist/source/Match.jsx.map +1 -1
  140. package/dist/source/Matches.jsx +46 -46
  141. package/dist/source/Matches.jsx.map +1 -1
  142. package/dist/source/RouterProvider.d.ts +1 -1
  143. package/dist/source/RouterProvider.jsx +13 -9
  144. package/dist/source/RouterProvider.jsx.map +1 -1
  145. package/dist/source/Scripts.d.ts +2 -1
  146. package/dist/source/Scripts.jsx +46 -47
  147. package/dist/source/Scripts.jsx.map +1 -1
  148. package/dist/source/Transitioner.jsx +78 -42
  149. package/dist/source/Transitioner.jsx.map +1 -1
  150. package/dist/source/headContentUtils.d.ts +2 -1
  151. package/dist/source/headContentUtils.jsx +79 -80
  152. package/dist/source/headContentUtils.jsx.map +1 -1
  153. package/dist/source/link.jsx +145 -112
  154. package/dist/source/link.jsx.map +1 -1
  155. package/dist/source/matchContext.d.ts +8 -2
  156. package/dist/source/matchContext.jsx +7 -3
  157. package/dist/source/matchContext.jsx.map +1 -1
  158. package/dist/source/not-found.d.ts +1 -1
  159. package/dist/source/not-found.jsx +6 -5
  160. package/dist/source/not-found.jsx.map +1 -1
  161. package/dist/source/router.js +2 -1
  162. package/dist/source/router.js.map +1 -1
  163. package/dist/source/routerStores.d.ts +10 -0
  164. package/dist/source/routerStores.js +82 -0
  165. package/dist/source/routerStores.js.map +1 -0
  166. package/dist/source/ssr/RouterClient.jsx +1 -1
  167. package/dist/source/ssr/RouterClient.jsx.map +1 -1
  168. package/dist/source/ssr/renderRouterToStream.jsx +1 -1
  169. package/dist/source/ssr/renderRouterToStream.jsx.map +1 -1
  170. package/dist/source/ssr/renderRouterToString.jsx +1 -1
  171. package/dist/source/ssr/renderRouterToString.jsx.map +1 -1
  172. package/dist/source/useBlocker.jsx +19 -8
  173. package/dist/source/useBlocker.jsx.map +1 -1
  174. package/dist/source/useCanGoBack.d.ts +2 -1
  175. package/dist/source/useCanGoBack.js +4 -2
  176. package/dist/source/useCanGoBack.js.map +1 -1
  177. package/dist/source/useLoaderDeps.jsx +2 -3
  178. package/dist/source/useLoaderDeps.jsx.map +1 -1
  179. package/dist/source/useLocation.jsx +13 -3
  180. package/dist/source/useLocation.jsx.map +1 -1
  181. package/dist/source/useMatch.jsx +33 -23
  182. package/dist/source/useMatch.jsx.map +1 -1
  183. package/dist/source/useParams.jsx +1 -1
  184. package/dist/source/useParams.jsx.map +1 -1
  185. package/dist/source/useRouterState.jsx +14 -55
  186. package/dist/source/useRouterState.jsx.map +1 -1
  187. package/dist/source/useSearch.jsx +2 -1
  188. package/dist/source/useSearch.jsx.map +1 -1
  189. package/dist/source/utils.d.ts +0 -5
  190. package/dist/source/utils.js +2 -15
  191. package/dist/source/utils.js.map +1 -1
  192. package/package.json +2 -2
  193. package/skills/solid-router/SKILL.md +2 -0
  194. package/src/Asset.tsx +3 -3
  195. package/src/HeadContent.dev.tsx +10 -1
  196. package/src/HeadContent.tsx +10 -1
  197. package/src/Match.tsx +395 -349
  198. package/src/Matches.tsx +55 -54
  199. package/src/RouterProvider.tsx +13 -10
  200. package/src/Scripts.tsx +55 -54
  201. package/src/Transitioner.tsx +101 -58
  202. package/src/headContentUtils.tsx +104 -96
  203. package/src/link.tsx +188 -146
  204. package/src/matchContext.tsx +16 -7
  205. package/src/not-found.tsx +6 -6
  206. package/src/router.ts +2 -1
  207. package/src/routerStores.ts +119 -0
  208. package/src/ssr/RouterClient.tsx +1 -1
  209. package/src/ssr/renderRouterToStream.tsx +1 -1
  210. package/src/ssr/renderRouterToString.tsx +1 -1
  211. package/src/useBlocker.tsx +80 -63
  212. package/src/useCanGoBack.ts +6 -2
  213. package/src/useLoaderDeps.tsx +2 -3
  214. package/src/useLocation.tsx +18 -5
  215. package/src/useMatch.tsx +37 -38
  216. package/src/useParams.tsx +2 -3
  217. package/src/useRouterState.tsx +21 -67
  218. package/src/useSearch.tsx +2 -1
  219. package/src/utils.ts +2 -24
package/src/Match.tsx CHANGED
@@ -11,159 +11,194 @@ import {
11
11
  import { isServer } from '@tanstack/router-core/isServer'
12
12
  import { Dynamic } from '@solidjs/web'
13
13
  import { CatchBoundary, ErrorComponent } from './CatchBoundary'
14
- import { useRouterState } from './useRouterState'
15
14
  import { useRouter } from './useRouter'
16
15
  import { CatchNotFound } from './not-found'
17
- import { matchContext } from './matchContext'
16
+ import { nearestMatchContext } from './matchContext'
18
17
  import { SafeFragment } from './SafeFragment'
19
18
  import { renderRouteNotFound } from './renderRouteNotFound'
20
19
  import { ScrollRestoration } from './scroll-restoration'
21
20
  import type { AnyRoute, RootRouteOptions } from '@tanstack/router-core'
22
21
 
23
- const MatchContext = matchContext as unknown as Solid.Component<{
22
+ const NearestMatchContext = nearestMatchContext as unknown as Solid.Component<{
24
23
  value: any
25
24
  children?: any
26
25
  }>
27
26
 
28
27
  export const Match = (props: { matchId: string }) => {
29
28
  const router = useRouter()
30
- const matchState = useRouterState({
31
- select: (s) => {
32
- const match = s.matches.find((d) => d.id === props.matchId)
33
-
34
- // During navigation transitions, matches can be temporarily removed
35
- // Return null to avoid errors - the component will handle this gracefully
36
- if (!match) {
37
- return null
38
- }
39
29
 
40
- return {
41
- routeId: match.routeId,
42
- ssr: match.ssr,
43
- _displayPending: match._displayPending,
44
- }
45
- },
30
+ const match = Solid.createMemo(() => {
31
+ const id = props.matchId
32
+ if (!id) return undefined
33
+ return router.stores.activeMatchStoresById.get(id)?.state
46
34
  })
47
35
 
48
- // If match doesn't exist yet, return null (component is being unmounted or not ready)
49
- if (!Solid.untrack(matchState)) return null
36
+ const rawMatchState = Solid.createMemo(() => {
37
+ const currentMatch = match()
38
+ if (!currentMatch) {
39
+ return null
40
+ }
50
41
 
51
- const route: () => AnyRoute = () => router.routesById[matchState()!.routeId]
42
+ const routeId = currentMatch.routeId as string
43
+ const parentRouteId = (router.routesById[routeId] as AnyRoute)?.parentRoute
44
+ ?.id
52
45
 
53
- const resolvePendingComponent = () =>
54
- route().options.pendingComponent ?? router.options.defaultPendingComponent
46
+ return {
47
+ matchId: currentMatch.id,
48
+ routeId,
49
+ ssr: currentMatch.ssr,
50
+ _displayPending: currentMatch._displayPending,
51
+ parentRouteId: parentRouteId as string | undefined,
52
+ }
53
+ })
55
54
 
56
- const routeErrorComponent = () =>
57
- route().options.errorComponent ?? router.options.defaultErrorComponent
55
+ const hasPendingMatch = Solid.createMemo(() => {
56
+ const currentRouteId = rawMatchState()?.routeId
57
+ return currentRouteId
58
+ ? Boolean(router.stores.pendingRouteIds.state[currentRouteId])
59
+ : false
60
+ })
61
+ const nearestMatch = {
62
+ matchId: () => rawMatchState()?.matchId,
63
+ routeId: () => rawMatchState()?.routeId,
64
+ match,
65
+ hasPending: hasPendingMatch,
66
+ }
58
67
 
59
- const routeOnCatch = () =>
60
- route().options.onCatch ?? router.options.defaultOnCatch
68
+ return (
69
+ <Solid.Show when={rawMatchState()}>
70
+ {(currentMatchState) => {
71
+ const route = Solid.createMemo(
72
+ () => router.routesById[currentMatchState().routeId] as AnyRoute,
73
+ )
61
74
 
62
- const routeNotFoundComponent = () =>
63
- route().isRoot
64
- ? // If it's the root route, use the globalNotFound option, with fallback to the notFoundRoute's component
65
- (route().options.notFoundComponent ??
66
- router.options.notFoundRoute?.options.component)
67
- : route().options.notFoundComponent
75
+ const resolvePendingComponent = Solid.createMemo(
76
+ () =>
77
+ route().options.pendingComponent ??
78
+ router.options.defaultPendingComponent,
79
+ )
68
80
 
69
- const resolvedNoSsr = Solid.createMemo(
70
- () => matchState()!.ssr === false || matchState()!.ssr === 'data-only',
71
- )
81
+ const routeErrorComponent = Solid.createMemo(
82
+ () =>
83
+ route().options.errorComponent ??
84
+ router.options.defaultErrorComponent,
85
+ )
72
86
 
73
- const ResolvedSuspenseBoundary = () =>
74
- resolvedNoSsr() ? SafeFragment : Solid.Loading
87
+ const routeOnCatch = Solid.createMemo(
88
+ () => route().options.onCatch ?? router.options.defaultOnCatch,
89
+ )
75
90
 
76
- const ResolvedCatchBoundary = () =>
77
- routeErrorComponent() ? CatchBoundary : SafeFragment
91
+ const routeNotFoundComponent = Solid.createMemo(() =>
92
+ route().isRoot
93
+ ? // If it's the root route, use the globalNotFound option, with fallback to the notFoundRoute's component
94
+ (route().options.notFoundComponent ??
95
+ router.options.notFoundRoute?.options.component)
96
+ : route().options.notFoundComponent,
97
+ )
78
98
 
79
- const ResolvedNotFoundBoundary = () =>
80
- routeNotFoundComponent() ? CatchNotFound : SafeFragment
99
+ const resolvedNoSsr = Solid.createMemo(
100
+ () =>
101
+ currentMatchState().ssr === false ||
102
+ currentMatchState().ssr === 'data-only',
103
+ )
81
104
 
82
- const resetKey = useRouterState({
83
- select: (s) => s.loadedAt,
84
- })
105
+ const ResolvedSuspenseBoundary = Solid.createMemo(() =>
106
+ resolvedNoSsr() ? SafeFragment : Solid.Loading,
107
+ )
85
108
 
86
- const parentRouteId = useRouterState({
87
- select: (s) => {
88
- const index = s.matches.findIndex((d) => d.id === props.matchId)
89
- return s.matches[index - 1]?.routeId as string
90
- },
91
- })
109
+ const ResolvedCatchBoundary = Solid.createMemo(() =>
110
+ routeErrorComponent() ? CatchBoundary : SafeFragment,
111
+ )
92
112
 
93
- const ShellComponent = Solid.createMemo(() =>
94
- route().isRoot
95
- ? ((route().options as RootRouteOptions).shellComponent ?? SafeFragment)
96
- : SafeFragment,
97
- )
113
+ const ResolvedNotFoundBoundary = Solid.createMemo(() =>
114
+ routeNotFoundComponent() ? CatchNotFound : SafeFragment,
115
+ )
98
116
 
99
- return (
100
- <Dynamic component={ShellComponent()}>
101
- <MatchContext value={() => props.matchId}>
102
- <Dynamic
103
- component={ResolvedSuspenseBoundary()}
104
- fallback={
105
- // Don't show fallback on server when using no-ssr mode to avoid hydration mismatch
106
- (isServer ?? router.isServer) || resolvedNoSsr() ? undefined : (
107
- <Dynamic component={resolvePendingComponent()} />
108
- )
109
- }
110
- >
111
- <Dynamic
112
- component={ResolvedCatchBoundary()}
113
- getResetKey={() => resetKey()}
114
- errorComponent={routeErrorComponent() || ErrorComponent}
115
- onCatch={(error: Error) => {
116
- // Forward not found errors (we don't want to show the error component for these)
117
- if (isNotFound(error)) throw error
118
- warning(
119
- false,
120
- `Error in route match: ${Solid.untrack(matchState)!.routeId}`,
121
- )
122
- routeOnCatch()?.(error)
123
- }}
124
- >
125
- <Dynamic
126
- component={ResolvedNotFoundBoundary()}
127
- fallback={(error: any) => {
128
- // If the current not found handler doesn't exist or it has a
129
- // route ID which doesn't match the current route, rethrow the error
130
- if (
131
- !routeNotFoundComponent() ||
132
- (error.routeId && error.routeId !== matchState()!.routeId) ||
133
- (!error.routeId && !route().isRoot)
134
- )
135
- throw error
117
+ const ShellComponent = Solid.createMemo(() =>
118
+ route().isRoot
119
+ ? ((route().options as RootRouteOptions).shellComponent ??
120
+ SafeFragment)
121
+ : SafeFragment,
122
+ )
136
123
 
137
- return (
138
- <Dynamic component={routeNotFoundComponent()} {...error} />
139
- )
140
- }}
141
- >
142
- <Solid.Switch>
143
- <Solid.Match when={resolvedNoSsr()}>
144
- <Solid.Show
145
- when={!(isServer ?? router.isServer)}
146
- fallback={<Dynamic component={resolvePendingComponent()} />}
124
+ return (
125
+ <Dynamic component={ShellComponent()}>
126
+ <NearestMatchContext value={nearestMatch}>
127
+ <Dynamic
128
+ component={ResolvedSuspenseBoundary()}
129
+ fallback={
130
+ // Don't show fallback on server when using no-ssr mode to avoid hydration mismatch
131
+ (isServer ?? router.isServer) &&
132
+ resolvedNoSsr() ? undefined : (
133
+ <Dynamic component={resolvePendingComponent()} />
134
+ )
135
+ }
136
+ >
137
+ <Dynamic
138
+ component={ResolvedCatchBoundary()}
139
+ getResetKey={() => router.stores.loadedAt.state}
140
+ errorComponent={routeErrorComponent() || ErrorComponent}
141
+ onCatch={(error: Error) => {
142
+ // Forward not found errors (we don't want to show the error component for these)
143
+ if (isNotFound(error)) throw error
144
+ warning(
145
+ false,
146
+ `Error in route match: ${currentMatchState().routeId}`,
147
+ )
148
+ routeOnCatch()?.(error)
149
+ }}
150
+ >
151
+ <Dynamic
152
+ component={ResolvedNotFoundBoundary()}
153
+ fallback={(error: any) => {
154
+ // If the current not found handler doesn't exist or it has a
155
+ // route ID which doesn't match the current route, rethrow the error
156
+ if (
157
+ !routeNotFoundComponent() ||
158
+ (error.routeId &&
159
+ error.routeId !== currentMatchState().routeId) ||
160
+ (!error.routeId && !route().isRoot)
161
+ )
162
+ throw error
163
+
164
+ return (
165
+ <Dynamic
166
+ component={routeNotFoundComponent()}
167
+ {...error}
168
+ />
169
+ )
170
+ }}
147
171
  >
148
- <MatchInner matchId={props.matchId} />
149
- </Solid.Show>
150
- </Solid.Match>
151
- <Solid.Match when={!resolvedNoSsr()}>
152
- <MatchInner matchId={props.matchId} />
153
- </Solid.Match>
154
- </Solid.Switch>
155
- </Dynamic>
172
+ <Solid.Switch>
173
+ <Solid.Match when={resolvedNoSsr()}>
174
+ <Solid.Show
175
+ when={!(isServer ?? router.isServer)}
176
+ fallback={
177
+ <Dynamic component={resolvePendingComponent()} />
178
+ }
179
+ >
180
+ <MatchInner />
181
+ </Solid.Show>
182
+ </Solid.Match>
183
+ <Solid.Match when={!resolvedNoSsr()}>
184
+ <MatchInner />
185
+ </Solid.Match>
186
+ </Solid.Switch>
187
+ </Dynamic>
188
+ </Dynamic>
189
+ </Dynamic>
190
+ </NearestMatchContext>
191
+
192
+ {currentMatchState().parentRouteId === rootRouteId ? (
193
+ <>
194
+ <OnRendered />
195
+ <ScrollRestoration />
196
+ </>
197
+ ) : null}
156
198
  </Dynamic>
157
- </Dynamic>
158
- </MatchContext>
159
-
160
- {parentRouteId() === rootRouteId ? (
161
- <>
162
- <OnRendered />
163
- <ScrollRestoration />
164
- </>
165
- ) : null}
166
- </Dynamic>
199
+ )
200
+ }}
201
+ </Solid.Show>
167
202
  )
168
203
  }
169
204
 
@@ -185,265 +220,273 @@ const lastOnRenderedKey = new WeakMap<object, string>()
185
220
  function OnRendered() {
186
221
  const router = useRouter()
187
222
 
188
- const location = useRouterState({
189
- select: (s) => {
190
- return s.resolvedLocation?.state.__TSR_key
191
- },
192
- })
223
+ const location = Solid.createMemo(
224
+ () => router.stores.resolvedLocation.state?.state.__TSR_key,
225
+ )
226
+ const locationState = Solid.createMemo(() => router.stores.location.state)
227
+ const resolvedLocationState = Solid.createMemo(
228
+ () => router.stores.resolvedLocation.state,
229
+ )
193
230
  Solid.createEffect(
194
- () => [location()] as const,
195
- ([location]) => {
231
+ () => [location(), locationState(), resolvedLocationState()] as const,
232
+ ([location, currentLocationState, currentResolvedLocationState]) => {
196
233
  if (!location) return
197
234
  if (lastOnRenderedKey.get(router) === location) return
198
235
  lastOnRenderedKey.set(router, location)
199
236
  router.emit({
200
237
  type: 'onRendered',
201
- ...getLocationChangeInfo(router.state),
238
+ ...getLocationChangeInfo(
239
+ currentLocationState,
240
+ currentResolvedLocationState,
241
+ ),
202
242
  })
203
243
  },
204
244
  )
205
245
  return null
206
246
  }
207
247
 
208
- export const MatchInner = (props: { matchId: string }): any => {
248
+ export const MatchInner = (): any => {
209
249
  const router = useRouter()
250
+ const match = Solid.useContext(nearestMatchContext).match
210
251
 
211
- const matchState = useRouterState({
212
- select: (s) => {
213
- const match = s.matches.find((d) => d.id === props.matchId)
252
+ const rawMatchState = Solid.createMemo(() => {
253
+ const currentMatch = match()
254
+ if (!currentMatch) {
255
+ return null
256
+ }
214
257
 
215
- // During navigation transitions, matches can be temporarily removed
216
- if (!match) {
217
- return null
218
- }
258
+ const routeId = currentMatch.routeId as string
259
+
260
+ const remountFn =
261
+ (router.routesById[routeId] as AnyRoute).options.remountDeps ??
262
+ router.options.defaultRemountDeps
263
+ const remountDeps = remountFn?.({
264
+ routeId,
265
+ loaderDeps: currentMatch.loaderDeps,
266
+ params: currentMatch._strictParams,
267
+ search: currentMatch._strictSearch,
268
+ })
269
+ const key = remountDeps ? JSON.stringify(remountDeps) : undefined
270
+
271
+ return {
272
+ key,
273
+ routeId,
274
+ match: {
275
+ id: currentMatch.id,
276
+ status: currentMatch.status,
277
+ error: currentMatch.error,
278
+ _forcePending: currentMatch._forcePending ?? false,
279
+ _displayPending: currentMatch._displayPending ?? false,
280
+ },
281
+ }
282
+ })
219
283
 
220
- const routeId = match.routeId as string
284
+ return (
285
+ <Solid.Show when={rawMatchState()}>
286
+ {(currentMatchState) => {
287
+ const route = Solid.createMemo(
288
+ () => router.routesById[currentMatchState().routeId]!,
289
+ )
221
290
 
222
- const remountFn =
223
- (router.routesById[routeId] as AnyRoute).options.remountDeps ??
224
- router.options.defaultRemountDeps
225
- const remountDeps = remountFn?.({
226
- routeId,
227
- loaderDeps: match.loaderDeps,
228
- params: match._strictParams,
229
- search: match._strictSearch,
230
- })
231
- const key = remountDeps ? JSON.stringify(remountDeps) : undefined
232
-
233
- return {
234
- key,
235
- routeId,
236
- match: {
237
- id: match.id,
238
- status: match.status,
239
- error: match.error,
240
- _forcePending: match._forcePending,
241
- _displayPending: match._displayPending,
242
- },
243
- }
244
- },
245
- })
291
+ const currentMatch = Solid.createMemo(() => currentMatchState().match)
246
292
 
247
- if (!Solid.untrack(matchState)) return null
293
+ const componentKey = Solid.createMemo(
294
+ () => currentMatchState().key ?? currentMatchState().match.id,
295
+ )
248
296
 
249
- const route = () => router.routesById[matchState()!.routeId]!
297
+ const Comp = Solid.createMemo(
298
+ () => route().options.component ?? router.options.defaultComponent,
299
+ )
250
300
 
251
- const match = () => matchState()!.match
301
+ const OutComponent = Solid.createMemo(() => {
302
+ const C = Comp()
303
+ return C || Outlet
304
+ })
252
305
 
253
- const componentKey = () => matchState()!.key ?? matchState()!.match.id
306
+ const RenderOut = () => <Dynamic component={OutComponent()} />
254
307
 
255
- const out = () => {
256
- const currentRoute = Solid.untrack(route)
257
- const Comp =
258
- currentRoute.options.component ?? router.options.defaultComponent
259
- if (Comp) {
260
- return <Comp />
261
- }
262
- return <Outlet />
263
- }
308
+ const keyedOut = () => (
309
+ <Solid.Show when={componentKey()} keyed>
310
+ {(_key) => <RenderOut />}
311
+ </Solid.Show>
312
+ )
264
313
 
265
- const keyedOut = () => (
266
- <Solid.Show when={componentKey()} keyed>
267
- {(_key) => out()}
268
- </Solid.Show>
269
- )
314
+ return (
315
+ <Solid.Switch>
316
+ <Solid.Match when={currentMatch()._displayPending}>
317
+ {(_) => {
318
+ const displayPendingResult = Solid.createMemo(
319
+ () =>
320
+ router.getMatch(currentMatch().id)?._nonReactive
321
+ .displayPendingPromise,
322
+ )
270
323
 
271
- return (
272
- <Solid.Switch>
273
- <Solid.Match when={match()._displayPending}>
274
- {(_) => {
275
- const matchId = Solid.untrack(() => match().id)
276
- const displayPendingResult = Solid.createMemo(
277
- () => router.getMatch(matchId)?._nonReactive.displayPendingPromise,
278
- )
279
-
280
- return <>{displayPendingResult()}</>
281
- }}
282
- </Solid.Match>
283
- <Solid.Match when={match()._forcePending}>
284
- {(_) => {
285
- const matchId = Solid.untrack(() => match().id)
286
- const minPendingResult = Solid.createMemo(
287
- () => router.getMatch(matchId)?._nonReactive.minPendingPromise,
288
- )
289
-
290
- return <>{minPendingResult()}</>
291
- }}
292
- </Solid.Match>
293
- <Solid.Match when={match().status === 'pending'}>
294
- {(_) => {
295
- const currentMatch = Solid.untrack(match)
296
- const currentRoute = Solid.untrack(route)
297
- const pendingMinMs =
298
- currentRoute.options.pendingMinMs ??
299
- router.options.defaultPendingMinMs
300
-
301
- if (pendingMinMs) {
302
- const routerMatch = router.getMatch(currentMatch.id)
303
- if (routerMatch && !routerMatch._nonReactive.minPendingPromise) {
304
- // Create a promise that will resolve after the minPendingMs
305
- if (!(isServer ?? router.isServer)) {
306
- const minPendingPromise = createControlledPromise<void>()
307
-
308
- routerMatch._nonReactive.minPendingPromise = minPendingPromise
309
-
310
- setTimeout(() => {
311
- minPendingPromise.resolve()
312
- // We've handled the minPendingPromise, so we can delete it
313
- routerMatch._nonReactive.minPendingPromise = undefined
314
- }, pendingMinMs)
315
- }
316
- }
317
- }
324
+ return <>{displayPendingResult()}</>
325
+ }}
326
+ </Solid.Match>
327
+ <Solid.Match when={currentMatch()._forcePending}>
328
+ {(_) => {
329
+ const minPendingResult = Solid.createMemo(
330
+ () =>
331
+ router.getMatch(currentMatch().id)?._nonReactive
332
+ .minPendingPromise,
333
+ )
318
334
 
319
- const loaderResult = Solid.createMemo(async () => {
320
- await new Promise((r) => setTimeout(r, 0))
321
- return router.getMatch(currentMatch.id)?._nonReactive.loadPromise
322
- })
323
-
324
- const FallbackComponent =
325
- currentRoute.options.pendingComponent ??
326
- router.options.defaultPendingComponent
327
-
328
- return (
329
- <>
330
- {FallbackComponent && pendingMinMs > 0 ? (
331
- <Dynamic component={FallbackComponent} />
332
- ) : null}
333
- {loaderResult()}
334
- </>
335
- )
336
- }}
337
- </Solid.Match>
338
- <Solid.Match when={match().status === 'notFound'}>
339
- {(_) => {
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')
344
-
345
- // Use Show with keyed to ensure re-render when routeId changes
346
- return (
347
- <Solid.Show when={currentRouteId} keyed>
348
- {(_routeId) =>
349
- renderRouteNotFound(router, currentRoute, currentMatch.error)
350
- }
351
- </Solid.Show>
352
- )
353
- }}
354
- </Solid.Match>
355
- <Solid.Match when={match().status === 'redirected'}>
356
- {(_) => {
357
- const matchId = Solid.untrack(() => match().id)
358
- invariant(
359
- isRedirect(Solid.untrack(match).error),
360
- 'Expected a redirect error',
361
- )
362
-
363
- const loaderResult = Solid.createMemo(async () => {
364
- await new Promise((r) => setTimeout(r, 0))
365
- return router.getMatch(matchId)?._nonReactive.loadPromise
366
- })
367
-
368
- return <>{loaderResult()}</>
369
- }}
370
- </Solid.Match>
371
- <Solid.Match when={match().status === 'error'}>
372
- {(_) => {
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
- }
335
+ return <>{minPendingResult()}</>
336
+ }}
337
+ </Solid.Match>
338
+ <Solid.Match when={currentMatch().status === 'pending'}>
339
+ {(_) => {
340
+ const pendingMinMs = Solid.untrack(
341
+ () =>
342
+ route().options.pendingMinMs ??
343
+ router.options.defaultPendingMinMs,
344
+ )
345
+
346
+ if (pendingMinMs) {
347
+ const routerMatch = Solid.untrack(() =>
348
+ router.getMatch(currentMatch().id),
349
+ )
350
+ if (
351
+ routerMatch &&
352
+ !routerMatch._nonReactive.minPendingPromise
353
+ ) {
354
+ // Create a promise that will resolve after the minPendingMs
355
+ if (!(isServer ?? router.isServer)) {
356
+ const minPendingPromise = createControlledPromise<void>()
357
+
358
+ routerMatch._nonReactive.minPendingPromise =
359
+ minPendingPromise
360
+
361
+ setTimeout(() => {
362
+ minPendingPromise.resolve()
363
+ // We've handled the minPendingPromise, so we can delete it
364
+ routerMatch._nonReactive.minPendingPromise = undefined
365
+ }, pendingMinMs)
366
+ }
367
+ }
368
+ }
369
+
370
+ const loaderResult = Solid.createMemo(async () => {
371
+ await new Promise((r) => setTimeout(r, 0))
372
+ return router.getMatch(currentMatch().id)?._nonReactive
373
+ .loadPromise
374
+ })
375
+
376
+ const FallbackComponent = Solid.untrack(
377
+ () =>
378
+ route().options.pendingComponent ??
379
+ router.options.defaultPendingComponent,
380
+ )
381
+
382
+ return (
383
+ <>
384
+ {FallbackComponent && pendingMinMs > 0 ? (
385
+ <Dynamic component={FallbackComponent} />
386
+ ) : null}
387
+ {loaderResult()}
388
+ </>
389
+ )
390
+ }}
391
+ </Solid.Match>
392
+ <Solid.Match when={currentMatch().status === 'notFound'}>
393
+ {(_) => {
394
+ const matchError = Solid.untrack(() => currentMatch().error)
395
+ invariant(isNotFound(matchError), 'Expected a notFound error')
396
+
397
+ // Use Show with keyed to ensure re-render when routeId changes
398
+ return (
399
+ <Solid.Show when={currentMatchState().routeId} keyed>
400
+ {(_routeId) =>
401
+ Solid.untrack(() =>
402
+ renderRouteNotFound(router, route(), matchError),
403
+ )
404
+ }
405
+ </Solid.Show>
406
+ )
407
+ }}
408
+ </Solid.Match>
409
+ <Solid.Match when={currentMatch().status === 'redirected'}>
410
+ {(_) => {
411
+ const matchError = Solid.untrack(() => currentMatch().error)
412
+ invariant(isRedirect(matchError), 'Expected a redirect error')
413
+
414
+ return null
415
+ }}
416
+ </Solid.Match>
417
+ <Solid.Match when={currentMatch().status === 'error'}>
418
+ {(_) => {
419
+ const matchError = Solid.untrack(() => currentMatch().error)
420
+ if (isServer ?? router.isServer) {
421
+ const RouteErrorComponent =
422
+ (route().options.errorComponent ??
423
+ router.options.defaultErrorComponent) ||
424
+ ErrorComponent
425
+
426
+ return (
427
+ <RouteErrorComponent
428
+ error={matchError}
429
+ info={{
430
+ componentStack: '',
431
+ }}
432
+ />
433
+ )
434
+ }
389
435
 
390
- throw Solid.untrack(match).error
391
- }}
392
- </Solid.Match>
393
- <Solid.Match when={match().status === 'success'}>
394
- {keyedOut()}
395
- </Solid.Match>
396
- </Solid.Switch>
436
+ throw matchError
437
+ }}
438
+ </Solid.Match>
439
+ <Solid.Match when={currentMatch().status === 'success'}>
440
+ {keyedOut()}
441
+ </Solid.Match>
442
+ </Solid.Switch>
443
+ )
444
+ }}
445
+ </Solid.Show>
397
446
  )
398
447
  }
399
448
 
400
449
  export const Outlet = () => {
401
450
  const router = useRouter()
402
- const matchId = Solid.useContext(matchContext)
403
- const routeId = useRouterState({
404
- select: (s) => s.matches.find((d) => d.id === matchId())?.routeId as string,
405
- })
406
-
407
- const route = () => router.routesById[routeId()]!
408
-
409
- const parentGlobalNotFound = useRouterState({
410
- select: (s) => {
411
- const matches = s.matches
412
- const parentMatch = matches.find((d) => d.id === matchId())
451
+ const nearestParentMatch = Solid.useContext(nearestMatchContext)
452
+ const parentMatch = nearestParentMatch.match
453
+ const routeId = nearestParentMatch.routeId
454
+ const route = Solid.createMemo(() =>
455
+ routeId() ? router.routesById[routeId()!] : undefined,
456
+ )
413
457
 
414
- // During navigation transitions, parent match can be temporarily removed
415
- // Return false to avoid errors - the component will handle this gracefully
416
- if (!parentMatch) {
417
- return false
418
- }
458
+ const parentGlobalNotFound = Solid.createMemo(
459
+ () => parentMatch()?.globalNotFound ?? false,
460
+ )
419
461
 
420
- return parentMatch.globalNotFound
421
- },
462
+ const childMatchId = Solid.createMemo(() => {
463
+ const currentRouteId = routeId()
464
+ return currentRouteId
465
+ ? router.stores.childMatchIdByRouteId.state[currentRouteId]
466
+ : undefined
422
467
  })
423
468
 
424
- const childMatchId = useRouterState({
425
- select: (s) => {
426
- const matches = s.matches
427
- const index = matches.findIndex((d) => d.id === matchId())
428
- const v = matches[index + 1]?.id
429
- return v
430
- },
469
+ const childRouteId = Solid.createMemo(() => {
470
+ const id = childMatchId()
471
+ if (!id) return undefined
472
+ return router.stores.activeMatchStoresById.get(id)?.state.routeId
431
473
  })
432
474
 
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
- },
475
+ const childRoute = Solid.createMemo(() => {
476
+ const id = childRouteId()
477
+ return id ? (router.routesById[id] as AnyRoute) : undefined
439
478
  })
440
479
 
441
- const childMatchStatus = useRouterState({
442
- select: (s) => {
443
- const matches = s.matches
444
- const index = matches.findIndex((d) => d.id === matchId())
445
- return matches[index + 1]?.status
446
- },
480
+ const childPendingComponent = Solid.createMemo(
481
+ () =>
482
+ childRoute()?.options.pendingComponent ??
483
+ router.options.defaultPendingComponent,
484
+ )
485
+
486
+ const childMatchStatus = Solid.createMemo(() => {
487
+ const id = childMatchId()
488
+ if (!id) return undefined
489
+ return router.stores.activeMatchStoresById.get(id)?.state.status
447
490
  })
448
491
 
449
492
  // Only show not-found if we're not in a redirected state
@@ -454,14 +497,17 @@ export const Outlet = () => {
454
497
  <Solid.Show
455
498
  when={!shouldShowNotFound() && childMatchId()}
456
499
  fallback={
457
- <Solid.Show when={shouldShowNotFound()}>
458
- {renderRouteNotFound(router, route(), undefined)}
500
+ <Solid.Show when={shouldShowNotFound() && route()}>
501
+ {(resolvedRoute) =>
502
+ Solid.untrack(() =>
503
+ renderRouteNotFound(router, resolvedRoute(), undefined),
504
+ )
505
+ }
459
506
  </Solid.Show>
460
507
  }
461
508
  >
462
- {(matchIdAccessor) => {
463
- // Use a memo to avoid stale accessor errors while keeping reactivity
464
- const currentMatchId = Solid.createMemo(() => matchIdAccessor())
509
+ {(childMatchIdAccessor) => {
510
+ const currentMatchId = Solid.createMemo(() => childMatchIdAccessor())
465
511
 
466
512
  return (
467
513
  <Solid.Show
@@ -472,9 +518,9 @@ export const Outlet = () => {
472
518
  {(_routeId) => (
473
519
  <Solid.Loading
474
520
  fallback={
475
- <Dynamic
476
- component={router.options.defaultPendingComponent}
477
- />
521
+ childPendingComponent() ? (
522
+ <Dynamic component={childPendingComponent()} />
523
+ ) : null
478
524
  }
479
525
  >
480
526
  <Match matchId={currentMatchId()} />