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