@tanstack/react-router 1.31.22 → 1.31.24
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/Matches.cjs +11 -6
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/RouterProvider.cjs +1 -121
- package/dist/cjs/RouterProvider.cjs.map +1 -1
- package/dist/cjs/Transitioner.cjs +110 -0
- package/dist/cjs/Transitioner.cjs.map +1 -0
- package/dist/cjs/Transitioner.d.cts +1 -0
- package/dist/cjs/utils.cjs +11 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/Matches.js +13 -8
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/RouterProvider.js +2 -105
- package/dist/esm/RouterProvider.js.map +1 -1
- package/dist/esm/Transitioner.d.ts +1 -0
- package/dist/esm/Transitioner.js +93 -0
- package/dist/esm/Transitioner.js.map +1 -0
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +11 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/Matches.tsx +22 -14
- package/src/RouterProvider.tsx +3 -149
- package/src/Transitioner.tsx +126 -0
- package/src/utils.ts +12 -0
package/src/Matches.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { createControlledPromise, pick } from './utils'
|
|
|
8
8
|
import { CatchNotFound, DefaultGlobalNotFound, isNotFound } from './not-found'
|
|
9
9
|
import { isRedirect } from './redirects'
|
|
10
10
|
import { type AnyRouter, type RegisteredRouter } from './router'
|
|
11
|
+
import { Transitioner } from './Transitioner'
|
|
11
12
|
import type { ResolveRelativePath, ToOptions } from './link'
|
|
12
13
|
import type { AnyRoute, ReactNode, StaticDataRouteOption } from './route'
|
|
13
14
|
import type {
|
|
@@ -99,6 +100,25 @@ export type AnyRouteMatch = RouteMatch<any, any, any, any, any, any, any>
|
|
|
99
100
|
export function Matches() {
|
|
100
101
|
const router = useRouter()
|
|
101
102
|
|
|
103
|
+
const pendingElement = router.options.defaultPendingComponent ? (
|
|
104
|
+
<router.options.defaultPendingComponent />
|
|
105
|
+
) : null
|
|
106
|
+
|
|
107
|
+
const inner = (
|
|
108
|
+
<React.Suspense fallback={pendingElement}>
|
|
109
|
+
<Transitioner />
|
|
110
|
+
<MatchesInner />
|
|
111
|
+
</React.Suspense>
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return router.options.InnerWrap ? (
|
|
115
|
+
<router.options.InnerWrap>{inner}</router.options.InnerWrap>
|
|
116
|
+
) : (
|
|
117
|
+
inner
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function MatchesInner() {
|
|
102
122
|
const matchId = useRouterState({
|
|
103
123
|
select: (s) => {
|
|
104
124
|
return s.matches[0]?.id
|
|
@@ -109,7 +129,7 @@ export function Matches() {
|
|
|
109
129
|
select: (s) => s.resolvedLocation.state.key!,
|
|
110
130
|
})
|
|
111
131
|
|
|
112
|
-
|
|
132
|
+
return (
|
|
113
133
|
<matchContext.Provider value={matchId}>
|
|
114
134
|
<CatchBoundary
|
|
115
135
|
getResetKey={() => resetKey}
|
|
@@ -126,12 +146,6 @@ export function Matches() {
|
|
|
126
146
|
</CatchBoundary>
|
|
127
147
|
</matchContext.Provider>
|
|
128
148
|
)
|
|
129
|
-
|
|
130
|
-
return router.options.InnerWrap ? (
|
|
131
|
-
<router.options.InnerWrap>{inner}</router.options.InnerWrap>
|
|
132
|
-
) : (
|
|
133
|
-
inner
|
|
134
|
-
)
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
function SafeFragment(props: any) {
|
|
@@ -221,13 +235,7 @@ export function Match({ matchId }: { matchId: string }) {
|
|
|
221
235
|
)
|
|
222
236
|
}
|
|
223
237
|
|
|
224
|
-
function MatchInner({
|
|
225
|
-
matchId,
|
|
226
|
-
// pendingElement,
|
|
227
|
-
}: {
|
|
228
|
-
matchId: string
|
|
229
|
-
// pendingElement: any
|
|
230
|
-
}): any {
|
|
238
|
+
function MatchInner({ matchId }: { matchId: string }): any {
|
|
231
239
|
const router = useRouter()
|
|
232
240
|
const routeId = useRouterState({
|
|
233
241
|
select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
|
package/src/RouterProvider.tsx
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
1
2
|
import * as React from 'react'
|
|
2
|
-
import { flushSync } from 'react-dom'
|
|
3
3
|
import { Matches } from './Matches'
|
|
4
|
-
import { pick, useLayoutEffect } from './utils'
|
|
5
|
-
import { useRouter } from './useRouter'
|
|
6
|
-
import { useRouterState } from './useRouterState'
|
|
7
4
|
import { getRouterContext } from './routerContext'
|
|
5
|
+
import { Transitioner } from './Transitioner'
|
|
8
6
|
import type { NavigateOptions, ToOptions } from './link'
|
|
9
7
|
import type { ParsedLocation } from './location'
|
|
10
8
|
import type { AnyRoute } from './route'
|
|
@@ -87,17 +85,8 @@ export function RouterContextProvider<
|
|
|
87
85
|
|
|
88
86
|
const routerContext = getRouterContext()
|
|
89
87
|
|
|
90
|
-
const pendingElement = router.options.defaultPendingComponent ? (
|
|
91
|
-
<router.options.defaultPendingComponent />
|
|
92
|
-
) : null
|
|
93
|
-
|
|
94
88
|
const provider = (
|
|
95
|
-
<
|
|
96
|
-
<routerContext.Provider value={router}>
|
|
97
|
-
{children}
|
|
98
|
-
<Transitioner />
|
|
99
|
-
</routerContext.Provider>
|
|
100
|
-
</React.Suspense>
|
|
89
|
+
<routerContext.Provider value={router}>{children}</routerContext.Provider>
|
|
101
90
|
)
|
|
102
91
|
|
|
103
92
|
if (router.options.Wrap) {
|
|
@@ -118,129 +107,6 @@ export function RouterProvider<
|
|
|
118
107
|
)
|
|
119
108
|
}
|
|
120
109
|
|
|
121
|
-
function Transitioner() {
|
|
122
|
-
const router = useRouter()
|
|
123
|
-
const mountLoadForRouter = React.useRef({ router, mounted: false })
|
|
124
|
-
const routerState = useRouterState({
|
|
125
|
-
select: (s) =>
|
|
126
|
-
pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning']),
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const [isTransitioning, startReactTransition_] = React.useTransition()
|
|
130
|
-
// Track pending state changes
|
|
131
|
-
const hasPendingMatches = useRouterState({
|
|
132
|
-
select: (s) => s.matches.some((d) => d.status === 'pending'),
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
const previousIsLoading = usePrevious(routerState.isLoading)
|
|
136
|
-
|
|
137
|
-
const isAnyPending =
|
|
138
|
-
routerState.isLoading || isTransitioning || hasPendingMatches
|
|
139
|
-
const previousIsAnyPending = usePrevious(isAnyPending)
|
|
140
|
-
|
|
141
|
-
router.startReactTransition = startReactTransition_
|
|
142
|
-
|
|
143
|
-
const tryLoad = async () => {
|
|
144
|
-
try {
|
|
145
|
-
await router.load()
|
|
146
|
-
} catch (err) {
|
|
147
|
-
console.error(err)
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Subscribe to location changes
|
|
152
|
-
// and try to load the new location
|
|
153
|
-
useLayoutEffect(() => {
|
|
154
|
-
const unsub = router.history.subscribe(router.load)
|
|
155
|
-
|
|
156
|
-
const nextLocation = router.buildLocation({
|
|
157
|
-
to: router.latestLocation.pathname,
|
|
158
|
-
search: true,
|
|
159
|
-
params: true,
|
|
160
|
-
hash: true,
|
|
161
|
-
state: true,
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
if (routerState.location.href !== nextLocation.href) {
|
|
165
|
-
router.commitLocation({ ...nextLocation, replace: true })
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return () => {
|
|
169
|
-
unsub()
|
|
170
|
-
}
|
|
171
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
172
|
-
}, [router, router.history])
|
|
173
|
-
|
|
174
|
-
// Try to load the initial location
|
|
175
|
-
useLayoutEffect(() => {
|
|
176
|
-
if (
|
|
177
|
-
window.__TSR_DEHYDRATED__ ||
|
|
178
|
-
(mountLoadForRouter.current.router === router &&
|
|
179
|
-
mountLoadForRouter.current.mounted)
|
|
180
|
-
) {
|
|
181
|
-
return
|
|
182
|
-
}
|
|
183
|
-
mountLoadForRouter.current = { router, mounted: true }
|
|
184
|
-
tryLoad()
|
|
185
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
186
|
-
}, [router])
|
|
187
|
-
|
|
188
|
-
useLayoutEffect(() => {
|
|
189
|
-
// The router was loading and now it's not
|
|
190
|
-
if (previousIsLoading && !routerState.isLoading) {
|
|
191
|
-
const toLocation = router.state.location
|
|
192
|
-
const fromLocation = router.state.resolvedLocation
|
|
193
|
-
const pathChanged = fromLocation.href !== toLocation.href
|
|
194
|
-
|
|
195
|
-
router.emit({
|
|
196
|
-
type: 'onLoad',
|
|
197
|
-
fromLocation,
|
|
198
|
-
toLocation,
|
|
199
|
-
pathChanged,
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
// if (router.viewTransitionPromise) {
|
|
203
|
-
// console.log('resolving view transition promise')
|
|
204
|
-
// }
|
|
205
|
-
|
|
206
|
-
// router.viewTransitionPromise?.resolve(true)
|
|
207
|
-
}
|
|
208
|
-
}, [previousIsLoading, router, routerState.isLoading])
|
|
209
|
-
|
|
210
|
-
useLayoutEffect(() => {
|
|
211
|
-
// The router was pending and now it's not
|
|
212
|
-
if (previousIsAnyPending && !isAnyPending) {
|
|
213
|
-
const toLocation = router.state.location
|
|
214
|
-
const fromLocation = router.state.resolvedLocation
|
|
215
|
-
const pathChanged = fromLocation.href !== toLocation.href
|
|
216
|
-
|
|
217
|
-
router.emit({
|
|
218
|
-
type: 'onResolved',
|
|
219
|
-
fromLocation,
|
|
220
|
-
toLocation,
|
|
221
|
-
pathChanged,
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
router.__store.setState((s) => ({
|
|
225
|
-
...s,
|
|
226
|
-
status: 'idle',
|
|
227
|
-
resolvedLocation: s.location,
|
|
228
|
-
}))
|
|
229
|
-
|
|
230
|
-
if ((document as any).querySelector) {
|
|
231
|
-
if (router.state.location.hash !== '') {
|
|
232
|
-
const el = document.getElementById(router.state.location.hash)
|
|
233
|
-
if (el) {
|
|
234
|
-
el.scrollIntoView()
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}, [isAnyPending, previousIsAnyPending, router])
|
|
240
|
-
|
|
241
|
-
return null
|
|
242
|
-
}
|
|
243
|
-
|
|
244
110
|
export function getRouteMatch<TRouteTree extends AnyRoute>(
|
|
245
111
|
state: RouterState<TRouteTree>,
|
|
246
112
|
id: string,
|
|
@@ -275,15 +141,3 @@ export type RouterProps<
|
|
|
275
141
|
>['context']
|
|
276
142
|
>
|
|
277
143
|
}
|
|
278
|
-
|
|
279
|
-
function usePrevious<T>(value: T): T {
|
|
280
|
-
const ref = React.useRef<T>(value)
|
|
281
|
-
|
|
282
|
-
if (ref.current !== value) {
|
|
283
|
-
const prevValue = ref.current
|
|
284
|
-
ref.current = value
|
|
285
|
-
return prevValue
|
|
286
|
-
} else {
|
|
287
|
-
return ref.current
|
|
288
|
-
}
|
|
289
|
-
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { pick, useLayoutEffect, usePrevious } from './utils'
|
|
3
|
+
import { useRouter } from './useRouter'
|
|
4
|
+
import { useRouterState } from './useRouterState'
|
|
5
|
+
|
|
6
|
+
export function Transitioner() {
|
|
7
|
+
const router = useRouter()
|
|
8
|
+
const mountLoadForRouter = React.useRef({ router, mounted: false })
|
|
9
|
+
const routerState = useRouterState({
|
|
10
|
+
select: (s) =>
|
|
11
|
+
pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning']),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const [isTransitioning, startReactTransition_] = React.useTransition()
|
|
15
|
+
// Track pending state changes
|
|
16
|
+
const hasPendingMatches = useRouterState({
|
|
17
|
+
select: (s) => s.matches.some((d) => d.status === 'pending'),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const previousIsLoading = usePrevious(routerState.isLoading)
|
|
21
|
+
|
|
22
|
+
const isAnyPending =
|
|
23
|
+
routerState.isLoading || isTransitioning || hasPendingMatches
|
|
24
|
+
const previousIsAnyPending = usePrevious(isAnyPending)
|
|
25
|
+
|
|
26
|
+
router.startReactTransition = startReactTransition_
|
|
27
|
+
|
|
28
|
+
const tryLoad = async () => {
|
|
29
|
+
try {
|
|
30
|
+
await router.load()
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error(err)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Subscribe to location changes
|
|
37
|
+
// and try to load the new location
|
|
38
|
+
useLayoutEffect(() => {
|
|
39
|
+
const unsub = router.history.subscribe(router.load)
|
|
40
|
+
|
|
41
|
+
const nextLocation = router.buildLocation({
|
|
42
|
+
to: router.latestLocation.pathname,
|
|
43
|
+
search: true,
|
|
44
|
+
params: true,
|
|
45
|
+
hash: true,
|
|
46
|
+
state: true,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (routerState.location.href !== nextLocation.href) {
|
|
50
|
+
router.commitLocation({ ...nextLocation, replace: true })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
unsub()
|
|
55
|
+
}
|
|
56
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
57
|
+
}, [router, router.history])
|
|
58
|
+
|
|
59
|
+
// Try to load the initial location
|
|
60
|
+
useLayoutEffect(() => {
|
|
61
|
+
if (
|
|
62
|
+
window.__TSR_DEHYDRATED__ ||
|
|
63
|
+
(mountLoadForRouter.current.router === router &&
|
|
64
|
+
mountLoadForRouter.current.mounted)
|
|
65
|
+
) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
mountLoadForRouter.current = { router, mounted: true }
|
|
69
|
+
tryLoad()
|
|
70
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
|
+
}, [router])
|
|
72
|
+
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
// The router was loading and now it's not
|
|
75
|
+
if (previousIsLoading && !routerState.isLoading) {
|
|
76
|
+
const toLocation = router.state.location
|
|
77
|
+
const fromLocation = router.state.resolvedLocation
|
|
78
|
+
const pathChanged = fromLocation.href !== toLocation.href
|
|
79
|
+
|
|
80
|
+
router.emit({
|
|
81
|
+
type: 'onLoad',
|
|
82
|
+
fromLocation,
|
|
83
|
+
toLocation,
|
|
84
|
+
pathChanged,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// if (router.viewTransitionPromise) {
|
|
88
|
+
// console.log('resolving view transition promise')
|
|
89
|
+
// }
|
|
90
|
+
// router.viewTransitionPromise?.resolve(true)
|
|
91
|
+
}
|
|
92
|
+
}, [previousIsLoading, router, routerState.isLoading])
|
|
93
|
+
|
|
94
|
+
useLayoutEffect(() => {
|
|
95
|
+
// The router was pending and now it's not
|
|
96
|
+
if (previousIsAnyPending && !isAnyPending) {
|
|
97
|
+
const toLocation = router.state.location
|
|
98
|
+
const fromLocation = router.state.resolvedLocation
|
|
99
|
+
const pathChanged = fromLocation.href !== toLocation.href
|
|
100
|
+
|
|
101
|
+
router.emit({
|
|
102
|
+
type: 'onResolved',
|
|
103
|
+
fromLocation,
|
|
104
|
+
toLocation,
|
|
105
|
+
pathChanged,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
router.__store.setState((s) => ({
|
|
109
|
+
...s,
|
|
110
|
+
status: 'idle',
|
|
111
|
+
resolvedLocation: s.location,
|
|
112
|
+
}))
|
|
113
|
+
|
|
114
|
+
if ((document as any).querySelector) {
|
|
115
|
+
if (router.state.location.hash !== '') {
|
|
116
|
+
const el = document.getElementById(router.state.location.hash)
|
|
117
|
+
if (el) {
|
|
118
|
+
el.scrollIntoView()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}, [isAnyPending, previousIsAnyPending, router])
|
|
124
|
+
|
|
125
|
+
return null
|
|
126
|
+
}
|
package/src/utils.ts
CHANGED
|
@@ -352,3 +352,15 @@ export function removeLayoutSegments(routePath: string): string {
|
|
|
352
352
|
const newSegments = segments.filter((segment) => !segment.startsWith('_'))
|
|
353
353
|
return newSegments.join('/')
|
|
354
354
|
}
|
|
355
|
+
|
|
356
|
+
export function usePrevious<T>(value: T): T {
|
|
357
|
+
const ref = React.useRef<T>(value)
|
|
358
|
+
|
|
359
|
+
if (ref.current !== value) {
|
|
360
|
+
const prevValue = ref.current
|
|
361
|
+
ref.current = value
|
|
362
|
+
return prevValue
|
|
363
|
+
} else {
|
|
364
|
+
return ref.current
|
|
365
|
+
}
|
|
366
|
+
}
|