@tanstack/solid-router 1.134.11 → 1.134.12
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/dist/cjs/Match.cjs +24 -14
- package/dist/cjs/Match.cjs.map +1 -1
- package/dist/cjs/Transitioner.cjs +8 -3
- package/dist/cjs/Transitioner.cjs.map +1 -1
- package/dist/cjs/useMatch.cjs +18 -5
- package/dist/cjs/useMatch.cjs.map +1 -1
- package/dist/esm/Match.js +24 -14
- package/dist/esm/Match.js.map +1 -1
- package/dist/esm/Transitioner.js +8 -3
- package/dist/esm/Transitioner.js.map +1 -1
- package/dist/esm/useMatch.js +18 -5
- package/dist/esm/useMatch.js.map +1 -1
- package/dist/source/Match.jsx +34 -21
- package/dist/source/Match.jsx.map +1 -1
- package/dist/source/Transitioner.jsx +9 -3
- package/dist/source/Transitioner.jsx.map +1 -1
- package/dist/source/useMatch.jsx +21 -5
- package/dist/source/useMatch.jsx.map +1 -1
- package/package.json +2 -2
- package/src/Match.tsx +54 -36
- package/src/Transitioner.tsx +9 -3
- package/src/useMatch.tsx +33 -9
package/src/Match.tsx
CHANGED
|
@@ -25,10 +25,12 @@ export const Match = (props: { matchId: string }) => {
|
|
|
25
25
|
select: (s) => {
|
|
26
26
|
const match = s.matches.find((d) => d.id === props.matchId)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
// During navigation transitions, matches can be temporarily removed
|
|
29
|
+
// Return null to avoid errors - the component will handle this gracefully
|
|
30
|
+
if (!match) {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
32
34
|
return {
|
|
33
35
|
routeId: match.routeId,
|
|
34
36
|
ssr: match.ssr,
|
|
@@ -37,9 +39,12 @@ export const Match = (props: { matchId: string }) => {
|
|
|
37
39
|
},
|
|
38
40
|
})
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
// If match doesn't exist yet, return null (component is being unmounted or not ready)
|
|
43
|
+
if (!matchState()) return null
|
|
41
44
|
|
|
42
|
-
const
|
|
45
|
+
const route: () => AnyRoute = () => router.routesById[matchState()!.routeId]
|
|
46
|
+
|
|
47
|
+
const resolvePendingComponent = () =>
|
|
43
48
|
route().options.pendingComponent ?? router.options.defaultPendingComponent
|
|
44
49
|
|
|
45
50
|
const routeErrorComponent = () =>
|
|
@@ -56,19 +61,9 @@ export const Match = (props: { matchId: string }) => {
|
|
|
56
61
|
: route().options.notFoundComponent
|
|
57
62
|
|
|
58
63
|
const resolvedNoSsr =
|
|
59
|
-
matchState()
|
|
60
|
-
|
|
61
|
-
const ResolvedSuspenseBoundary = () =>
|
|
62
|
-
// If we're on the root route, allow forcefully wrapping in suspense
|
|
63
|
-
(!route().isRoot ||
|
|
64
|
-
route().options.wrapInSuspense ||
|
|
65
|
-
resolvedNoSsr ||
|
|
66
|
-
matchState()._displayPending) &&
|
|
67
|
-
(route().options.wrapInSuspense ??
|
|
68
|
-
PendingComponent() ??
|
|
69
|
-
((route().options.errorComponent as any)?.preload || resolvedNoSsr))
|
|
70
|
-
? Solid.Suspense
|
|
71
|
-
: SafeFragment
|
|
64
|
+
matchState()!.ssr === false || matchState()!.ssr === 'data-only'
|
|
65
|
+
|
|
66
|
+
const ResolvedSuspenseBoundary = () => Solid.Suspense
|
|
72
67
|
|
|
73
68
|
const ResolvedCatchBoundary = () =>
|
|
74
69
|
routeErrorComponent() ? CatchBoundary : SafeFragment
|
|
@@ -99,7 +94,7 @@ export const Match = (props: { matchId: string }) => {
|
|
|
99
94
|
fallback={
|
|
100
95
|
// Don't show fallback on server when using no-ssr mode to avoid hydration mismatch
|
|
101
96
|
router.isServer || resolvedNoSsr ? undefined : (
|
|
102
|
-
<Dynamic component={
|
|
97
|
+
<Dynamic component={resolvePendingComponent()} />
|
|
103
98
|
)
|
|
104
99
|
}
|
|
105
100
|
>
|
|
@@ -121,7 +116,7 @@ export const Match = (props: { matchId: string }) => {
|
|
|
121
116
|
// route ID which doesn't match the current route, rethrow the error
|
|
122
117
|
if (
|
|
123
118
|
!routeNotFoundComponent() ||
|
|
124
|
-
(error.routeId && error.routeId !== matchState()
|
|
119
|
+
(error.routeId && error.routeId !== matchState()!.routeId) ||
|
|
125
120
|
(!error.routeId && !route().isRoot)
|
|
126
121
|
)
|
|
127
122
|
throw error
|
|
@@ -135,7 +130,7 @@ export const Match = (props: { matchId: string }) => {
|
|
|
135
130
|
<Solid.Match when={resolvedNoSsr}>
|
|
136
131
|
<Solid.Show
|
|
137
132
|
when={!router.isServer}
|
|
138
|
-
fallback={<Dynamic component={
|
|
133
|
+
fallback={<Dynamic component={resolvePendingComponent()} />}
|
|
139
134
|
>
|
|
140
135
|
<MatchInner matchId={props.matchId} />
|
|
141
136
|
</Solid.Show>
|
|
@@ -190,7 +185,13 @@ export const MatchInner = (props: { matchId: string }): any => {
|
|
|
190
185
|
|
|
191
186
|
const matchState = useRouterState({
|
|
192
187
|
select: (s) => {
|
|
193
|
-
const match = s.matches.find((d) => d.id === props.matchId)
|
|
188
|
+
const match = s.matches.find((d) => d.id === props.matchId)
|
|
189
|
+
|
|
190
|
+
// During navigation transitions, matches can be temporarily removed
|
|
191
|
+
if (!match) {
|
|
192
|
+
return null
|
|
193
|
+
}
|
|
194
|
+
|
|
194
195
|
const routeId = match.routeId as string
|
|
195
196
|
|
|
196
197
|
const remountFn =
|
|
@@ -218,11 +219,13 @@ export const MatchInner = (props: { matchId: string }): any => {
|
|
|
218
219
|
},
|
|
219
220
|
})
|
|
220
221
|
|
|
221
|
-
|
|
222
|
+
if (!matchState()) return null
|
|
223
|
+
|
|
224
|
+
const route = () => router.routesById[matchState()!.routeId]!
|
|
222
225
|
|
|
223
|
-
const match = () => matchState()
|
|
226
|
+
const match = () => matchState()!.match
|
|
224
227
|
|
|
225
|
-
const componentKey = () => matchState()
|
|
228
|
+
const componentKey = () => matchState()!.key ?? matchState()!.match.id
|
|
226
229
|
|
|
227
230
|
const out = () => {
|
|
228
231
|
const Comp = route().options.component ?? router.options.defaultComponent
|
|
@@ -287,7 +290,18 @@ export const MatchInner = (props: { matchId: string }): any => {
|
|
|
287
290
|
return router.getMatch(match().id)?._nonReactive.loadPromise
|
|
288
291
|
})
|
|
289
292
|
|
|
290
|
-
|
|
293
|
+
const FallbackComponent =
|
|
294
|
+
route().options.pendingComponent ??
|
|
295
|
+
router.options.defaultPendingComponent
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<>
|
|
299
|
+
{FallbackComponent ? (
|
|
300
|
+
<Dynamic component={FallbackComponent} />
|
|
301
|
+
) : null}
|
|
302
|
+
{loaderResult()}
|
|
303
|
+
</>
|
|
304
|
+
)
|
|
291
305
|
}}
|
|
292
306
|
</Solid.Match>
|
|
293
307
|
<Solid.Match when={match().status === 'notFound'}>
|
|
@@ -350,10 +364,13 @@ export const Outlet = () => {
|
|
|
350
364
|
select: (s) => {
|
|
351
365
|
const matches = s.matches
|
|
352
366
|
const parentMatch = matches.find((d) => d.id === matchId())
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
)
|
|
367
|
+
|
|
368
|
+
// During navigation transitions, parent match can be temporarily removed
|
|
369
|
+
// Return false to avoid errors - the component will handle this gracefully
|
|
370
|
+
if (!parentMatch) {
|
|
371
|
+
return false
|
|
372
|
+
}
|
|
373
|
+
|
|
357
374
|
return parentMatch.globalNotFound
|
|
358
375
|
},
|
|
359
376
|
})
|
|
@@ -388,20 +405,21 @@ export const Outlet = () => {
|
|
|
388
405
|
</Solid.Show>
|
|
389
406
|
}
|
|
390
407
|
>
|
|
391
|
-
{(
|
|
392
|
-
//
|
|
408
|
+
{(matchIdAccessor) => {
|
|
409
|
+
// Use a memo to avoid stale accessor errors while keeping reactivity
|
|
410
|
+
const currentMatchId = Solid.createMemo(() => matchIdAccessor())
|
|
393
411
|
|
|
394
412
|
return (
|
|
395
413
|
<Solid.Show
|
|
396
|
-
when={
|
|
397
|
-
fallback={<Match matchId={
|
|
414
|
+
when={currentMatchId() === rootRouteId}
|
|
415
|
+
fallback={<Match matchId={currentMatchId()} />}
|
|
398
416
|
>
|
|
399
417
|
<Solid.Suspense
|
|
400
418
|
fallback={
|
|
401
419
|
<Dynamic component={router.options.defaultPendingComponent} />
|
|
402
420
|
}
|
|
403
421
|
>
|
|
404
|
-
<Match matchId={
|
|
422
|
+
<Match matchId={currentMatchId()} />
|
|
405
423
|
</Solid.Suspense>
|
|
406
424
|
</Solid.Show>
|
|
407
425
|
)
|
package/src/Transitioner.tsx
CHANGED
|
@@ -20,6 +20,7 @@ export function Transitioner() {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
|
|
23
|
+
|
|
23
24
|
// Track pending state changes
|
|
24
25
|
const hasPendingMatches = useRouterState({
|
|
25
26
|
select: (s) => s.matches.some((d) => d.status === 'pending'),
|
|
@@ -34,10 +35,15 @@ export function Transitioner() {
|
|
|
34
35
|
const isPagePending = () => isLoading() || hasPendingMatches()
|
|
35
36
|
const previousIsPagePending = usePrevious(isPagePending)
|
|
36
37
|
|
|
37
|
-
router.startTransition =
|
|
38
|
+
router.startTransition = (fn: () => void | Promise<void>) => {
|
|
38
39
|
setIsTransitioning(true)
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
Solid.startTransition(async () => {
|
|
41
|
+
try {
|
|
42
|
+
await fn()
|
|
43
|
+
} finally {
|
|
44
|
+
setIsTransitioning(false)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
// Subscribe to location changes
|
package/src/useMatch.tsx
CHANGED
|
@@ -73,24 +73,48 @@ export function useMatch<
|
|
|
73
73
|
opts.from ? dummyMatchContext : matchContext,
|
|
74
74
|
)
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
// Create a signal to track error state separately from the match
|
|
77
|
+
const matchState: Solid.Accessor<{
|
|
78
|
+
match: any
|
|
79
|
+
shouldThrowError: boolean
|
|
80
|
+
}> = useRouterState({
|
|
77
81
|
select: (state: any) => {
|
|
78
82
|
const match = state.matches.find((d: any) =>
|
|
79
83
|
opts.from ? opts.from === d.routeId : d.id === nearestMatchId(),
|
|
80
84
|
)
|
|
81
85
|
|
|
82
|
-
invariant(
|
|
83
|
-
!((opts.shouldThrow ?? true) && !match),
|
|
84
|
-
`Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
86
|
if (match === undefined) {
|
|
88
|
-
|
|
87
|
+
// During navigation transitions, check if the match exists in pendingMatches
|
|
88
|
+
const pendingMatch = state.pendingMatches?.find((d: any) =>
|
|
89
|
+
opts.from ? opts.from === d.routeId : d.id === nearestMatchId(),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// Determine if we should throw an error
|
|
93
|
+
const shouldThrowError =
|
|
94
|
+
!pendingMatch && !state.isTransitioning && (opts.shouldThrow ?? true)
|
|
95
|
+
|
|
96
|
+
return { match: undefined, shouldThrowError }
|
|
89
97
|
}
|
|
90
98
|
|
|
91
|
-
return
|
|
99
|
+
return {
|
|
100
|
+
match: opts.select ? opts.select(match) : match,
|
|
101
|
+
shouldThrowError: false,
|
|
102
|
+
}
|
|
92
103
|
},
|
|
93
104
|
} as any)
|
|
94
105
|
|
|
95
|
-
|
|
106
|
+
// Use createEffect to throw errors outside the reactive selector context
|
|
107
|
+
// This allows error boundaries to properly catch the errors
|
|
108
|
+
Solid.createEffect(() => {
|
|
109
|
+
const state = matchState()
|
|
110
|
+
if (state.shouldThrowError) {
|
|
111
|
+
invariant(
|
|
112
|
+
false,
|
|
113
|
+
`Could not find ${opts.from ? `an active match from "${opts.from}"` : 'a nearest match!'}`,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Return an accessor that extracts just the match value
|
|
119
|
+
return Solid.createMemo(() => matchState().match) as any
|
|
96
120
|
}
|