@tanstack/solid-router 1.124.0 → 1.125.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.
package/src/Match.tsx CHANGED
@@ -22,18 +22,19 @@ import type { AnyRoute, RootRouteOptions } from '@tanstack/router-core'
22
22
 
23
23
  export const Match = (props: { matchId: string }) => {
24
24
  const router = useRouter()
25
- const routeId = useRouterState({
25
+ const matchState = useRouterState({
26
26
  select: (s) => {
27
- return s.matches.find((d) => d.id === props.matchId)?.routeId as string
27
+ const match = s.matches.find((d) => d.id === props.matchId)
28
+
29
+ invariant(
30
+ match,
31
+ `Could not find match for matchId "${props.matchId}". Please file an issue!`,
32
+ )
33
+ return pick(match, ['routeId', 'ssr', '_displayPending'])
28
34
  },
29
35
  })
30
36
 
31
- invariant(
32
- routeId,
33
- `Could not find routeId for matchId "${props.matchId}". Please file an issue!`,
34
- )
35
-
36
- const route: () => AnyRoute = () => router.routesById[routeId()]
37
+ const route: () => AnyRoute = () => router.routesById[matchState().routeId]
37
38
 
38
39
  const PendingComponent = () =>
39
40
  route().options.pendingComponent ?? router.options.defaultPendingComponent
@@ -51,12 +52,18 @@ export const Match = (props: { matchId: string }) => {
51
52
  router.options.notFoundRoute?.options.component)
52
53
  : route().options.notFoundComponent
53
54
 
55
+ const resolvedNoSsr =
56
+ matchState().ssr === false || matchState().ssr === 'data-only'
57
+
54
58
  const ResolvedSuspenseBoundary = () =>
55
59
  // If we're on the root route, allow forcefully wrapping in suspense
56
- (!route().isRoot || route().options.wrapInSuspense) &&
60
+ (!route().isRoot ||
61
+ route().options.wrapInSuspense ||
62
+ resolvedNoSsr ||
63
+ matchState()._displayPending) &&
57
64
  (route().options.wrapInSuspense ??
58
65
  PendingComponent() ??
59
- (route().options.errorComponent as any)?.preload)
66
+ ((route().options.errorComponent as any)?.preload || resolvedNoSsr))
60
67
  ? Solid.Suspense
61
68
  : SafeFragment
62
69
 
@@ -106,7 +113,7 @@ export const Match = (props: { matchId: string }) => {
106
113
  // route ID which doesn't match the current route, rethrow the error
107
114
  if (
108
115
  !routeNotFoundComponent() ||
109
- (error.routeId && error.routeId !== routeId) ||
116
+ (error.routeId && error.routeId !== matchState().routeId) ||
110
117
  (!error.routeId && !route().isRoot)
111
118
  )
112
119
  throw error
@@ -116,7 +123,19 @@ export const Match = (props: { matchId: string }) => {
116
123
  )
117
124
  }}
118
125
  >
119
- <MatchInner matchId={props.matchId} />
126
+ <Solid.Switch>
127
+ <Solid.Match when={resolvedNoSsr || router.isShell}>
128
+ <Solid.Show
129
+ when={!router.isServer}
130
+ fallback={<Dynamic component={PendingComponent()} />}
131
+ >
132
+ <MatchInner matchId={props.matchId} />
133
+ </Solid.Show>
134
+ </Solid.Match>
135
+ <Solid.Match when={!resolvedNoSsr}>
136
+ <MatchInner matchId={props.matchId} />
137
+ </Solid.Match>
138
+ </Solid.Switch>
120
139
  </Dynamic>
121
140
  </Dynamic>
122
141
  </Dynamic>
@@ -161,8 +180,7 @@ function OnRendered() {
161
180
  export const MatchInner = (props: { matchId: string }): any => {
162
181
  const router = useRouter()
163
182
 
164
- // { match, key, routeId } =
165
- const matchState: Solid.Accessor<any> = useRouterState({
183
+ const matchState = useRouterState({
166
184
  select: (s) => {
167
185
  const matchIndex = s.matches.findIndex((d) => d.id === props.matchId)
168
186
  const match = s.matches[matchIndex]!
@@ -182,31 +200,19 @@ export const MatchInner = (props: { matchId: string }): any => {
182
200
  return {
183
201
  key,
184
202
  routeId,
185
- match: pick(match, ['id', 'status', 'error']),
203
+ match: pick(match, [
204
+ 'id',
205
+ 'status',
206
+ 'error',
207
+ '_forcePending',
208
+ '_displayPending',
209
+ ]),
186
210
  }
187
211
  },
188
212
  })
189
213
 
190
214
  const route = () => router.routesById[matchState().routeId]!
191
215
 
192
- // function useChangedDiff(value: any) {
193
- // const ref = Solid.useRef(value)
194
- // const changed = ref.current !== value
195
- // if (changed) {
196
- // console.log(
197
- // 'Changed:',
198
- // value,
199
- // Object.fromEntries(
200
- // Object.entries(value).filter(
201
- // ([key, val]) => val !== ref.current[key],
202
- // ),
203
- // ),
204
- // )
205
- // }
206
- // ref.current = value
207
- // }
208
-
209
- // useChangedDiff(match)
210
216
  const match = () => matchState().match
211
217
 
212
218
  const out = () => {
@@ -219,47 +225,16 @@ export const MatchInner = (props: { matchId: string }): any => {
219
225
 
220
226
  return (
221
227
  <Solid.Switch>
222
- <Solid.Match when={match().status === 'notFound'}>
223
- {(_) => {
224
- invariant(isNotFound(match().error), 'Expected a notFound error')
225
-
226
- return renderRouteNotFound(router, route(), match().error)
227
- }}
228
- </Solid.Match>
229
- <Solid.Match when={match().status === 'redirected'}>
230
- {(_) => {
231
- invariant(isRedirect(match().error), 'Expected a redirect error')
232
-
233
- const [loaderResult] = Solid.createResource(async () => {
234
- await new Promise((r) => setTimeout(r, 0))
235
- return router.getMatch(match().id)?.loadPromise
236
- })
237
-
238
- return <>{loaderResult()}</>
239
- }}
240
- </Solid.Match>
241
- <Solid.Match when={match().status === 'error'}>
228
+ <Solid.Match when={match()._displayPending}>
242
229
  {(_) => {
243
- if (router.isServer) {
244
- const RouteErrorComponent =
245
- (route().options.errorComponent ??
246
- router.options.defaultErrorComponent) ||
247
- ErrorComponent
248
-
249
- return (
250
- <RouteErrorComponent
251
- error={match().error}
252
- info={{
253
- componentStack: '',
254
- }}
255
- />
256
- )
257
- }
230
+ const [displayPendingResult] = Solid.createResource(
231
+ () => router.getMatch(match().id)?.displayPendingPromise,
232
+ )
258
233
 
259
- throw match().error
234
+ return <>{displayPendingResult()}</>
260
235
  }}
261
236
  </Solid.Match>
262
- <Solid.Match when={match().status === 'pending'}>
237
+ <Solid.Match when={match().status === 'pending' || match()._forcePending}>
263
238
  {(_) => {
264
239
  const pendingMinMs =
265
240
  route().options.pendingMinMs ?? router.options.defaultPendingMinMs
@@ -296,6 +271,46 @@ export const MatchInner = (props: { matchId: string }): any => {
296
271
  return <>{loaderResult()}</>
297
272
  }}
298
273
  </Solid.Match>
274
+ <Solid.Match when={match().status === 'notFound'}>
275
+ {(_) => {
276
+ invariant(isNotFound(match().error), 'Expected a notFound error')
277
+
278
+ return renderRouteNotFound(router, route(), match().error)
279
+ }}
280
+ </Solid.Match>
281
+ <Solid.Match when={match().status === 'redirected'}>
282
+ {(_) => {
283
+ invariant(isRedirect(match().error), 'Expected a redirect error')
284
+
285
+ const [loaderResult] = Solid.createResource(async () => {
286
+ await new Promise((r) => setTimeout(r, 0))
287
+ return router.getMatch(match().id)?.loadPromise
288
+ })
289
+
290
+ return <>{loaderResult()}</>
291
+ }}
292
+ </Solid.Match>
293
+ <Solid.Match when={match().status === 'error'}>
294
+ {(_) => {
295
+ if (router.isServer) {
296
+ const RouteErrorComponent =
297
+ (route().options.errorComponent ??
298
+ router.options.defaultErrorComponent) ||
299
+ ErrorComponent
300
+
301
+ return (
302
+ <RouteErrorComponent
303
+ error={match().error}
304
+ info={{
305
+ componentStack: '',
306
+ }}
307
+ />
308
+ )
309
+ }
310
+
311
+ throw match().error
312
+ }}
313
+ </Solid.Match>
299
314
  <Solid.Match when={match().status === 'success'}>{out()}</Solid.Match>
300
315
  </Solid.Switch>
301
316
  )
@@ -333,16 +348,6 @@ export const Outlet = () => {
333
348
 
334
349
  return (
335
350
  <Solid.Switch>
336
- <Solid.Match when={router.isShell}>
337
- <Solid.Suspense
338
- fallback={
339
- <Dynamic component={router.options.defaultPendingComponent} />
340
- }
341
- >
342
- <ErrorComponent error={new Error('ShellBoundaryError')} />
343
- </Solid.Suspense>
344
- </Solid.Match>
345
-
346
351
  <Solid.Match when={parentGlobalNotFound()}>
347
352
  {renderRouteNotFound(router, route(), undefined)}
348
353
  </Solid.Match>
@@ -1,28 +1,14 @@
1
1
  import { Dynamic } from 'solid-js/web'
2
2
  import { createResource } from 'solid-js'
3
- import { Outlet } from './Match'
4
- import { ClientOnly } from './ClientOnly'
3
+ import { isModuleNotFoundError } from '@tanstack/router-core'
5
4
  import type { AsyncRouteComponent } from './route'
6
5
 
7
- // If the load fails due to module not found, it may mean a new version of
8
- // the build was deployed and the user's browser is still using an old version.
9
- // If this happens, the old version in the user's browser would have an outdated
10
- // URL to the lazy module.
11
- // In that case, we want to attempt one window refresh to get the latest.
12
- function isModuleNotFoundError(error: any): boolean {
13
- return (
14
- typeof error?.message === 'string' &&
15
- /Failed to fetch dynamically imported module/.test(error.message)
16
- )
17
- }
18
-
19
6
  export function lazyRouteComponent<
20
7
  T extends Record<string, any>,
21
8
  TKey extends keyof T = 'default',
22
9
  >(
23
10
  importer: () => Promise<T>,
24
11
  exportName?: TKey,
25
- ssr?: () => boolean,
26
12
  ): T[TKey] extends (props: infer TProps) => any
27
13
  ? AsyncRouteComponent<TProps>
28
14
  : never {
@@ -31,10 +17,6 @@ export function lazyRouteComponent<
31
17
  let error: any
32
18
 
33
19
  const load = () => {
34
- if (typeof document === 'undefined' && ssr?.() === false) {
35
- comp = (() => null) as any
36
- return Promise.resolve(comp)
37
- }
38
20
  if (!loadPromise) {
39
21
  loadPromise = importer()
40
22
  .then((res) => {
@@ -54,6 +36,11 @@ export function lazyRouteComponent<
54
36
  // Now that we're out of preload and into actual render path,
55
37
  // throw the error if it was a module not found error during preload
56
38
  if (error) {
39
+ // If the load fails due to module not found, it may mean a new version of
40
+ // the build was deployed and the user's browser is still using an old version.
41
+ // If this happens, the old version in the user's browser would have an outdated
42
+ // URL to the lazy module.
43
+ // In that case, we want to attempt one window refresh to get the latest.
57
44
  if (isModuleNotFoundError(error)) {
58
45
  // We don't want an error thrown from preload in this case, because
59
46
  // there's nothing we want to do about module not found during preload.
@@ -94,13 +81,6 @@ export function lazyRouteComponent<
94
81
  return <>{compResource()}</>
95
82
  }
96
83
 
97
- if (ssr?.() === false) {
98
- return (
99
- <ClientOnly fallback={<Outlet />}>
100
- <Dynamic component={comp} {...props} />
101
- </ClientOnly>
102
- )
103
- }
104
84
  return <Dynamic component={comp} {...props} />
105
85
  }
106
86