@tanstack/react-router 0.0.1-beta.235 → 0.0.1-beta.237
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/build/cjs/Matches.js +31 -18
- package/build/cjs/Matches.js.map +1 -1
- package/build/cjs/RouterProvider.js +45 -25
- package/build/cjs/RouterProvider.js.map +1 -1
- package/build/cjs/index.js +1 -0
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/route.js +13 -7
- package/build/cjs/route.js.map +1 -1
- package/build/cjs/router.js +49 -37
- package/build/cjs/router.js.map +1 -1
- package/build/cjs/useParams.js +7 -2
- package/build/cjs/useParams.js.map +1 -1
- package/build/cjs/useSearch.js +6 -1
- package/build/cjs/useSearch.js.map +1 -1
- package/build/cjs/utils.js +4 -1
- package/build/cjs/utils.js.map +1 -1
- package/build/esm/index.js +155 -92
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +574 -293
- package/build/types/Matches.d.ts +9 -3
- package/build/types/RouterProvider.d.ts +3 -0
- package/build/types/route.d.ts +30 -10
- package/build/types/router.d.ts +6 -3
- package/build/types/useParams.d.ts +3 -1
- package/build/types/useSearch.d.ts +3 -1
- package/build/types/utils.d.ts +3 -1
- package/build/umd/index.development.js +423 -95
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +2 -2
- package/build/umd/index.production.js.map +1 -1
- package/package.json +4 -2
- package/src/Matches.tsx +70 -35
- package/src/RouterProvider.tsx +68 -32
- package/src/route.ts +37 -15
- package/src/router.ts +62 -44
- package/src/useParams.tsx +14 -4
- package/src/useSearch.tsx +11 -3
- package/src/utils.ts +20 -12
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-router",
|
|
3
3
|
"author": "Tanner Linsley",
|
|
4
|
-
"version": "0.0.1-beta.
|
|
4
|
+
"version": "0.0.1-beta.237",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "tanstack/router",
|
|
7
7
|
"homepage": "https://tanstack.com/router",
|
|
@@ -40,9 +40,11 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@babel/runtime": "^7.16.7",
|
|
43
|
+
"@tanstack/react-store": "^0.2.1",
|
|
44
|
+
"@tanstack/store": "^0.1.3",
|
|
43
45
|
"tiny-invariant": "^1.3.1",
|
|
44
46
|
"tiny-warning": "^1.0.3",
|
|
45
|
-
"@tanstack/history": "0.0.1-beta.
|
|
47
|
+
"@tanstack/history": "0.0.1-beta.237"
|
|
46
48
|
},
|
|
47
49
|
"scripts": {
|
|
48
50
|
"build": "rollup --config rollup.config.js"
|
package/src/Matches.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import * as React from 'react'
|
|
|
2
2
|
import invariant from 'tiny-invariant'
|
|
3
3
|
import warning from 'tiny-warning'
|
|
4
4
|
import { CatchBoundary, ErrorComponent } from './CatchBoundary'
|
|
5
|
-
import { useRouter } from './RouterProvider'
|
|
5
|
+
import { useRouter, useRouterState } from './RouterProvider'
|
|
6
6
|
import { ResolveRelativePath, ToOptions } from './link'
|
|
7
7
|
import { AnyRoute, ReactNode, rootRouteId } from './route'
|
|
8
8
|
import {
|
|
@@ -48,8 +48,11 @@ export type AnyRouteMatch = RouteMatch<any>
|
|
|
48
48
|
|
|
49
49
|
export function Matches() {
|
|
50
50
|
const router = useRouter()
|
|
51
|
-
const
|
|
52
|
-
const
|
|
51
|
+
const routerState = useRouterState()
|
|
52
|
+
const matches = routerState.pendingMatches?.some((d) => d.showPending)
|
|
53
|
+
? routerState.pendingMatches
|
|
54
|
+
: routerState.matches
|
|
55
|
+
const locationKey = router.latestLocation.state.key
|
|
53
56
|
const route = router.routesById[rootRouteId]!
|
|
54
57
|
|
|
55
58
|
const errorComponent = React.useCallback(
|
|
@@ -294,20 +297,28 @@ export function useMatch<
|
|
|
294
297
|
TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
|
|
295
298
|
TStrict extends boolean = true,
|
|
296
299
|
TRouteMatchState = RouteMatch<TRouteTree, TFrom>,
|
|
300
|
+
TSelected = TRouteMatchState,
|
|
297
301
|
>(
|
|
298
|
-
opts: StrictOrFrom<TFrom
|
|
299
|
-
|
|
300
|
-
|
|
302
|
+
opts: StrictOrFrom<TFrom> & {
|
|
303
|
+
select?: (match: TRouteMatchState) => TSelected
|
|
304
|
+
},
|
|
305
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
301
306
|
const nearestMatch = React.useContext(matchesContext)[0]!
|
|
302
307
|
const nearestMatchRouteId = nearestMatch?.routeId
|
|
303
308
|
|
|
304
|
-
const matchRouteId = (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
309
|
+
const matchRouteId = useRouterState({
|
|
310
|
+
select: (state) => {
|
|
311
|
+
const matches = state.pendingMatches?.some((d) => d.showPending)
|
|
312
|
+
? state.pendingMatches
|
|
313
|
+
: state.matches
|
|
314
|
+
|
|
315
|
+
const match = opts?.from
|
|
316
|
+
? matches.find((d) => d.routeId === opts?.from)
|
|
317
|
+
: matches.find((d) => d.id === nearestMatch.id)
|
|
308
318
|
|
|
309
|
-
|
|
310
|
-
|
|
319
|
+
return match!.routeId
|
|
320
|
+
},
|
|
321
|
+
})
|
|
311
322
|
|
|
312
323
|
if (opts?.strict ?? true) {
|
|
313
324
|
invariant(
|
|
@@ -322,32 +333,51 @@ export function useMatch<
|
|
|
322
333
|
)
|
|
323
334
|
}
|
|
324
335
|
|
|
325
|
-
const matchSelection = (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
match
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
+
const matchSelection = useRouterState({
|
|
337
|
+
select: (state) => {
|
|
338
|
+
const matches = state.pendingMatches?.some((d) => d.showPending)
|
|
339
|
+
? state.pendingMatches
|
|
340
|
+
: state.matches
|
|
341
|
+
|
|
342
|
+
const match = opts?.from
|
|
343
|
+
? matches.find((d) => d.routeId === opts?.from)
|
|
344
|
+
: matches.find((d) => d.id === nearestMatch.id)
|
|
345
|
+
|
|
346
|
+
invariant(
|
|
347
|
+
match,
|
|
348
|
+
`Could not find ${
|
|
349
|
+
opts?.from
|
|
350
|
+
? `an active match from "${opts.from}"`
|
|
351
|
+
: 'a nearest match!'
|
|
352
|
+
}`,
|
|
353
|
+
)
|
|
336
354
|
|
|
337
|
-
|
|
338
|
-
|
|
355
|
+
return opts?.select ? opts.select(match as any) : match
|
|
356
|
+
},
|
|
357
|
+
})
|
|
339
358
|
|
|
340
359
|
return matchSelection as any
|
|
341
360
|
}
|
|
342
361
|
|
|
343
362
|
export const matchesContext = React.createContext<RouteMatch[]>(null!)
|
|
344
363
|
|
|
345
|
-
export function useMatches
|
|
346
|
-
|
|
364
|
+
export function useMatches<T = RouteMatch[]>(opts?: {
|
|
365
|
+
select?: (matches: RouteMatch[]) => T
|
|
366
|
+
}): T {
|
|
347
367
|
const contextMatches = React.useContext(matchesContext)
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
368
|
+
|
|
369
|
+
return useRouterState({
|
|
370
|
+
select: (state) => {
|
|
371
|
+
let matches = state.pendingMatches?.some((d) => d.showPending)
|
|
372
|
+
? state.pendingMatches
|
|
373
|
+
: state.matches
|
|
374
|
+
|
|
375
|
+
matches = matches.slice(
|
|
376
|
+
matches.findIndex((d) => d.id === contextMatches[0]?.id),
|
|
377
|
+
)
|
|
378
|
+
return opts?.select ? opts.select(matches) : (matches as T)
|
|
379
|
+
},
|
|
380
|
+
})
|
|
351
381
|
}
|
|
352
382
|
|
|
353
383
|
export function useLoaderData<
|
|
@@ -358,10 +388,15 @@ export function useLoaderData<
|
|
|
358
388
|
TRouteTree,
|
|
359
389
|
TFrom
|
|
360
390
|
>,
|
|
391
|
+
TSelected = TRouteMatch['loaderData'],
|
|
361
392
|
>(
|
|
362
|
-
opts: StrictOrFrom<TFrom
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
393
|
+
opts: StrictOrFrom<TFrom> & {
|
|
394
|
+
select?: (match: TRouteMatch) => TSelected
|
|
395
|
+
},
|
|
396
|
+
): TStrict extends true ? TSelected : TSelected | undefined {
|
|
397
|
+
const match = useMatch({ ...opts, select: undefined })!
|
|
398
|
+
|
|
399
|
+
return typeof opts.select === 'function'
|
|
400
|
+
? opts.select(match?.loaderData)
|
|
401
|
+
: match?.loaderData
|
|
367
402
|
}
|
package/src/RouterProvider.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
2
|
import warning from 'tiny-warning'
|
|
3
|
+
import { useStore } from '@tanstack/react-store'
|
|
3
4
|
import { Matches } from './Matches'
|
|
4
5
|
import {
|
|
5
6
|
LinkInfo,
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
RouterOptions,
|
|
19
20
|
RouterState,
|
|
20
21
|
} from './router'
|
|
21
|
-
import { NoInfer, PickAsRequired, useLayoutEffect } from './utils'
|
|
22
|
+
import { NoInfer, PickAsRequired, pick, useLayoutEffect } from './utils'
|
|
22
23
|
import { MatchRouteOptions } from './Matches'
|
|
23
24
|
import { RouteMatch } from './Matches'
|
|
24
25
|
|
|
@@ -86,7 +87,11 @@ export function RouterProvider<
|
|
|
86
87
|
},
|
|
87
88
|
} as any)
|
|
88
89
|
|
|
89
|
-
const inner =
|
|
90
|
+
const inner = (
|
|
91
|
+
<routerContext.Provider value={router}>
|
|
92
|
+
<RouterProviderInner<TRouteTree, TDehydrated> router={router} />
|
|
93
|
+
</routerContext.Provider>
|
|
94
|
+
)
|
|
90
95
|
|
|
91
96
|
if (router.options.Wrap) {
|
|
92
97
|
return <router.options.Wrap>{inner}</router.options.Wrap>
|
|
@@ -99,27 +104,44 @@ function RouterProviderInner<
|
|
|
99
104
|
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
|
|
100
105
|
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
101
106
|
>({ router }: RouterProps<TRouteTree, TDehydrated>) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const state = React.useMemo<RouterState<TRouteTree>>(
|
|
108
|
-
() => ({
|
|
109
|
-
...preState,
|
|
110
|
-
status: isAnyTransitioning ? 'pending' : 'idle',
|
|
111
|
-
location: isTransitioning ? router.latestLocation : preState.location,
|
|
112
|
-
pendingMatches: router.pendingMatches,
|
|
113
|
-
}),
|
|
114
|
-
[preState, isTransitioning],
|
|
107
|
+
return (
|
|
108
|
+
<>
|
|
109
|
+
<Matches />
|
|
110
|
+
<Transitioner />
|
|
111
|
+
</>
|
|
115
112
|
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function Transitioner() {
|
|
116
|
+
const router = useRouter()
|
|
117
|
+
const routerState = useRouterState({
|
|
118
|
+
select: (s) =>
|
|
119
|
+
pick(s, ['isLoading', 'location', 'resolvedLocation', 'isTransitioning']),
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const [isTransitioning, startReactTransition] = React.useTransition()
|
|
116
123
|
|
|
117
|
-
router.setState = setState
|
|
118
|
-
router.state = state
|
|
119
124
|
router.startReactTransition = startReactTransition
|
|
120
125
|
|
|
126
|
+
React.useEffect(() => {
|
|
127
|
+
if (isTransitioning) {
|
|
128
|
+
router.__store.setState((s) => ({
|
|
129
|
+
...s,
|
|
130
|
+
isTransitioning,
|
|
131
|
+
}))
|
|
132
|
+
}
|
|
133
|
+
}, [isTransitioning])
|
|
134
|
+
|
|
121
135
|
const tryLoad = () => {
|
|
122
|
-
|
|
136
|
+
const apply = (cb: () => void) => {
|
|
137
|
+
if (!routerState.isTransitioning) {
|
|
138
|
+
startReactTransition(() => cb())
|
|
139
|
+
} else {
|
|
140
|
+
cb()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
apply(() => {
|
|
123
145
|
try {
|
|
124
146
|
router.load()
|
|
125
147
|
} catch (err) {
|
|
@@ -131,7 +153,7 @@ function RouterProviderInner<
|
|
|
131
153
|
useLayoutEffect(() => {
|
|
132
154
|
const unsub = router.history.subscribe(() => {
|
|
133
155
|
router.latestLocation = router.parseLocation(router.latestLocation)
|
|
134
|
-
if (
|
|
156
|
+
if (routerState.location !== router.latestLocation) {
|
|
135
157
|
tryLoad()
|
|
136
158
|
}
|
|
137
159
|
})
|
|
@@ -143,7 +165,7 @@ function RouterProviderInner<
|
|
|
143
165
|
state: true,
|
|
144
166
|
})
|
|
145
167
|
|
|
146
|
-
if (
|
|
168
|
+
if (routerState.location.href !== nextLocation.href) {
|
|
147
169
|
router.commitLocation({ ...nextLocation, replace: true })
|
|
148
170
|
}
|
|
149
171
|
|
|
@@ -153,21 +175,28 @@ function RouterProviderInner<
|
|
|
153
175
|
}, [router.history])
|
|
154
176
|
|
|
155
177
|
useLayoutEffect(() => {
|
|
156
|
-
if (
|
|
178
|
+
if (
|
|
179
|
+
!isTransitioning &&
|
|
180
|
+
!routerState.isLoading &&
|
|
181
|
+
routerState.resolvedLocation !== routerState.location
|
|
182
|
+
) {
|
|
183
|
+
console.log('onResolved', routerState.location)
|
|
157
184
|
router.emit({
|
|
158
185
|
type: 'onResolved',
|
|
159
|
-
fromLocation:
|
|
160
|
-
toLocation:
|
|
161
|
-
pathChanged:
|
|
186
|
+
fromLocation: routerState.resolvedLocation,
|
|
187
|
+
toLocation: routerState.location,
|
|
188
|
+
pathChanged:
|
|
189
|
+
routerState.location!.href !== routerState.resolvedLocation?.href,
|
|
162
190
|
})
|
|
163
191
|
router.pendingMatches = []
|
|
164
192
|
|
|
165
|
-
setState((s) => ({
|
|
193
|
+
router.__store.setState((s) => ({
|
|
166
194
|
...s,
|
|
195
|
+
isTransitioning: false,
|
|
167
196
|
resolvedLocation: s.location,
|
|
168
197
|
}))
|
|
169
198
|
}
|
|
170
|
-
})
|
|
199
|
+
}, [isTransitioning, routerState.isLoading])
|
|
171
200
|
|
|
172
201
|
useLayoutEffect(() => {
|
|
173
202
|
if (!window.__TSR_DEHYDRATED__) {
|
|
@@ -175,18 +204,25 @@ function RouterProviderInner<
|
|
|
175
204
|
}
|
|
176
205
|
}, [])
|
|
177
206
|
|
|
178
|
-
return
|
|
179
|
-
<routerContext.Provider value={router}>
|
|
180
|
-
<Matches />
|
|
181
|
-
</routerContext.Provider>
|
|
182
|
-
)
|
|
207
|
+
return null
|
|
183
208
|
}
|
|
184
209
|
|
|
185
210
|
export function getRouteMatch<TRouteTree extends AnyRoute>(
|
|
186
211
|
state: RouterState<TRouteTree>,
|
|
187
212
|
id: string,
|
|
188
213
|
): undefined | RouteMatch<TRouteTree> {
|
|
189
|
-
return [...state.pendingMatches, ...state.matches].find(
|
|
214
|
+
return [...(state.pendingMatches ?? []), ...state.matches].find(
|
|
215
|
+
(d) => d.id === id,
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function useRouterState<
|
|
220
|
+
TSelected = RouterState<RegisteredRouter['routeTree']>,
|
|
221
|
+
>(opts?: {
|
|
222
|
+
select: (state: RouterState<RegisteredRouter['routeTree']>) => TSelected
|
|
223
|
+
}): TSelected {
|
|
224
|
+
const router = useRouter()
|
|
225
|
+
return useStore(router.__store, opts?.select as any)
|
|
190
226
|
}
|
|
191
227
|
|
|
192
228
|
export type RouterProps<
|
package/src/route.ts
CHANGED
|
@@ -590,22 +590,34 @@ export class Route<
|
|
|
590
590
|
// replaced by a framework specific implementation if necessary
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
-
useMatch =
|
|
594
|
-
|
|
593
|
+
useMatch = <TSelected = TAllContext>(opts?: {
|
|
594
|
+
select?: (search: TAllContext) => TSelected
|
|
595
|
+
}): TSelected => {
|
|
596
|
+
return useMatch({ ...opts, from: this.id }) as any
|
|
595
597
|
}
|
|
596
|
-
useRouteContext =
|
|
598
|
+
useRouteContext = <TSelected = TAllContext>(opts?: {
|
|
599
|
+
select?: (search: TAllContext) => TSelected
|
|
600
|
+
}): TSelected => {
|
|
597
601
|
return useMatch({
|
|
602
|
+
...opts,
|
|
598
603
|
from: this.id,
|
|
599
|
-
|
|
604
|
+
select: (d: any) => (opts?.select ? opts.select(d.context) : d.context),
|
|
605
|
+
} as any)
|
|
600
606
|
}
|
|
601
|
-
useSearch =
|
|
602
|
-
|
|
607
|
+
useSearch = <TSelected = TFullSearchSchema>(opts?: {
|
|
608
|
+
select?: (search: TFullSearchSchema) => TSelected
|
|
609
|
+
}): TSelected => {
|
|
610
|
+
return useSearch({ ...opts, from: this.id } as any)
|
|
603
611
|
}
|
|
604
|
-
useParams =
|
|
605
|
-
|
|
612
|
+
useParams = <TSelected = TAllParams>(opts?: {
|
|
613
|
+
select?: (search: TAllParams) => TSelected
|
|
614
|
+
}): TSelected => {
|
|
615
|
+
return useParams({ ...opts, from: this.id } as any)
|
|
606
616
|
}
|
|
607
|
-
useLoaderData =
|
|
608
|
-
|
|
617
|
+
useLoaderData = <TSelected = TLoaderData>(opts?: {
|
|
618
|
+
select?: (search: TLoaderData) => TSelected
|
|
619
|
+
}): TSelected => {
|
|
620
|
+
return useLoaderData({ ...opts, from: this.id } as any) as any
|
|
609
621
|
}
|
|
610
622
|
}
|
|
611
623
|
|
|
@@ -756,11 +768,21 @@ export type RouteProps<
|
|
|
756
768
|
TAllContext extends Record<string, any> = AnyContext,
|
|
757
769
|
TLoaderData extends any = unknown,
|
|
758
770
|
> = {
|
|
759
|
-
useMatch:
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
771
|
+
useMatch: <TSelected = TAllContext>(opts?: {
|
|
772
|
+
select?: (search: TAllContext) => TSelected
|
|
773
|
+
}) => TSelected
|
|
774
|
+
useRouteContext: <TSelected = TAllContext>(opts?: {
|
|
775
|
+
select?: (search: TAllContext) => TSelected
|
|
776
|
+
}) => TSelected
|
|
777
|
+
useSearch: <TSelected = TFullSearchSchema>(opts?: {
|
|
778
|
+
select?: (search: TFullSearchSchema) => TSelected
|
|
779
|
+
}) => TSelected
|
|
780
|
+
useParams: <TSelected = TAllParams>(opts?: {
|
|
781
|
+
select?: (search: TAllParams) => TSelected
|
|
782
|
+
}) => TSelected
|
|
783
|
+
useLoaderData: <TSelected = TLoaderData>(opts?: {
|
|
784
|
+
select?: (search: TLoaderData) => TSelected
|
|
785
|
+
}) => TSelected
|
|
764
786
|
}
|
|
765
787
|
|
|
766
788
|
export type ErrorRouteProps<
|
package/src/router.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createBrowserHistory,
|
|
6
6
|
createMemoryHistory,
|
|
7
7
|
} from '@tanstack/history'
|
|
8
|
+
import { Store } from '@tanstack/store'
|
|
8
9
|
|
|
9
10
|
//
|
|
10
11
|
|
|
@@ -29,7 +30,6 @@ import {
|
|
|
29
30
|
functionalUpdate,
|
|
30
31
|
last,
|
|
31
32
|
pick,
|
|
32
|
-
PickAsPartial,
|
|
33
33
|
} from './utils'
|
|
34
34
|
import {
|
|
35
35
|
ErrorRouteComponent,
|
|
@@ -134,8 +134,10 @@ export interface RouterOptions<
|
|
|
134
134
|
|
|
135
135
|
export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
|
|
136
136
|
status: 'pending' | 'idle'
|
|
137
|
+
isLoading: boolean
|
|
138
|
+
isTransitioning: boolean
|
|
137
139
|
matches: RouteMatch<TRouteTree>[]
|
|
138
|
-
pendingMatches
|
|
140
|
+
pendingMatches?: RouteMatch<TRouteTree>[]
|
|
139
141
|
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
140
142
|
resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
141
143
|
lastUpdated: number
|
|
@@ -236,7 +238,7 @@ export class Router<
|
|
|
236
238
|
dehydratedData?: TDehydrated
|
|
237
239
|
|
|
238
240
|
// Must build in constructor
|
|
239
|
-
|
|
241
|
+
__store!: Store<RouterState<TRouteTree>>
|
|
240
242
|
options!: PickAsRequired<
|
|
241
243
|
RouterOptions<TRouteTree, TDehydrated>,
|
|
242
244
|
'stringifySearch' | 'parseSearch' | 'context'
|
|
@@ -265,11 +267,6 @@ export class Router<
|
|
|
265
267
|
// by the router provider once rendered. We provide these so that the
|
|
266
268
|
// router can be used in a non-react environment if necessary
|
|
267
269
|
startReactTransition: (fn: () => void) => void = (fn) => fn()
|
|
268
|
-
setState: (updater: NonNullableUpdater<RouterState<TRouteTree>>) => void = (
|
|
269
|
-
updater,
|
|
270
|
-
) => {
|
|
271
|
-
this.state = functionalUpdate(updater, this.state)
|
|
272
|
-
}
|
|
273
270
|
|
|
274
271
|
update = (newOptions: RouterConstructorOptions<TRouteTree, TDehydrated>) => {
|
|
275
272
|
this.options = {
|
|
@@ -296,11 +293,25 @@ export class Router<
|
|
|
296
293
|
this.buildRouteTree()
|
|
297
294
|
}
|
|
298
295
|
|
|
299
|
-
if (!this.
|
|
300
|
-
this.
|
|
296
|
+
if (!this.__store) {
|
|
297
|
+
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
298
|
+
onUpdate: () => {
|
|
299
|
+
this.__store.state = {
|
|
300
|
+
...this.state,
|
|
301
|
+
status:
|
|
302
|
+
this.state.isTransitioning || this.state.isLoading
|
|
303
|
+
? 'pending'
|
|
304
|
+
: 'idle',
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
})
|
|
301
308
|
}
|
|
302
309
|
}
|
|
303
310
|
|
|
311
|
+
get state() {
|
|
312
|
+
return this.__store.state
|
|
313
|
+
}
|
|
314
|
+
|
|
304
315
|
buildRouteTree = () => {
|
|
305
316
|
this.routesById = {} as RoutesById<TRouteTree>
|
|
306
317
|
this.routesByPath = {} as RoutesByPath<TRouteTree>
|
|
@@ -654,7 +665,7 @@ export class Router<
|
|
|
654
665
|
}
|
|
655
666
|
|
|
656
667
|
cancelMatches = () => {
|
|
657
|
-
this.state.
|
|
668
|
+
this.state.pendingMatches?.forEach((match) => {
|
|
658
669
|
this.cancelMatch(match.id)
|
|
659
670
|
})
|
|
660
671
|
}
|
|
@@ -948,6 +959,15 @@ export class Router<
|
|
|
948
959
|
let latestPromise
|
|
949
960
|
let firstBadMatchIndex: number | undefined
|
|
950
961
|
|
|
962
|
+
const updatePendingMatch = (match: AnyRouteMatch) => {
|
|
963
|
+
this.__store.setState((s) => ({
|
|
964
|
+
...s,
|
|
965
|
+
pendingMatches: s.pendingMatches?.map((d) =>
|
|
966
|
+
d.id === match.id ? match : d,
|
|
967
|
+
),
|
|
968
|
+
}))
|
|
969
|
+
}
|
|
970
|
+
|
|
951
971
|
// Check each match middleware to see if the route can be accessed
|
|
952
972
|
try {
|
|
953
973
|
for (let [index, match] of matches.entries()) {
|
|
@@ -1149,13 +1169,12 @@ export class Router<
|
|
|
1149
1169
|
}
|
|
1150
1170
|
|
|
1151
1171
|
if (!preload) {
|
|
1152
|
-
|
|
1153
|
-
...s,
|
|
1154
|
-
matches: s.matches.map((d) => (d.id === match.id ? match : d)),
|
|
1155
|
-
}))
|
|
1172
|
+
updatePendingMatch(match)
|
|
1156
1173
|
}
|
|
1157
1174
|
|
|
1158
1175
|
let didShowPending = false
|
|
1176
|
+
const pendingMinMs =
|
|
1177
|
+
route.options.pendingMinMs ?? this.options.defaultPendingMinMs
|
|
1159
1178
|
|
|
1160
1179
|
await new Promise<void>(async (resolve) => {
|
|
1161
1180
|
// If the route has a pending component and a pendingMs option,
|
|
@@ -1170,12 +1189,7 @@ export class Router<
|
|
|
1170
1189
|
showPending: true,
|
|
1171
1190
|
}
|
|
1172
1191
|
|
|
1173
|
-
|
|
1174
|
-
...s,
|
|
1175
|
-
matches: s.matches.map((d) =>
|
|
1176
|
-
d.id === match.id ? match : d,
|
|
1177
|
-
),
|
|
1178
|
-
}))
|
|
1192
|
+
updatePendingMatch(match)
|
|
1179
1193
|
resolve()
|
|
1180
1194
|
})
|
|
1181
1195
|
}
|
|
@@ -1184,9 +1198,6 @@ export class Router<
|
|
|
1184
1198
|
const loaderData = await loadPromise
|
|
1185
1199
|
if ((latestPromise = checkLatest())) return await latestPromise
|
|
1186
1200
|
|
|
1187
|
-
const pendingMinMs =
|
|
1188
|
-
route.options.pendingMinMs ?? this.options.defaultPendingMinMs
|
|
1189
|
-
|
|
1190
1201
|
if (didShowPending && pendingMinMs) {
|
|
1191
1202
|
await new Promise((r) => setTimeout(r, pendingMinMs))
|
|
1192
1203
|
}
|
|
@@ -1220,13 +1231,22 @@ export class Router<
|
|
|
1220
1231
|
isFetching: false,
|
|
1221
1232
|
updatedAt: Date.now(),
|
|
1222
1233
|
}
|
|
1234
|
+
} finally {
|
|
1235
|
+
// If we showed the pending component, that means
|
|
1236
|
+
// we already moved the pendingMatches to the matches
|
|
1237
|
+
// state, so we need to update that specific match
|
|
1238
|
+
if (didShowPending && pendingMinMs && match.showPending) {
|
|
1239
|
+
this.__store.setState((s) => ({
|
|
1240
|
+
...s,
|
|
1241
|
+
matches: s.matches?.map((d) =>
|
|
1242
|
+
d.id === match.id ? match : d,
|
|
1243
|
+
),
|
|
1244
|
+
}))
|
|
1245
|
+
}
|
|
1223
1246
|
}
|
|
1224
1247
|
|
|
1225
1248
|
if (!preload) {
|
|
1226
|
-
|
|
1227
|
-
...s,
|
|
1228
|
-
matches: s.matches.map((d) => (d.id === match.id ? match : d)),
|
|
1229
|
-
}))
|
|
1249
|
+
updatePendingMatch(match)
|
|
1230
1250
|
}
|
|
1231
1251
|
|
|
1232
1252
|
resolve()
|
|
@@ -1262,7 +1282,7 @@ export class Router<
|
|
|
1262
1282
|
})
|
|
1263
1283
|
|
|
1264
1284
|
// Match the routes
|
|
1265
|
-
let
|
|
1285
|
+
let pendingMatches: RouteMatch<any, any>[] = this.matchRoutes(
|
|
1266
1286
|
next.pathname,
|
|
1267
1287
|
next.search,
|
|
1268
1288
|
{
|
|
@@ -1270,23 +1290,21 @@ export class Router<
|
|
|
1270
1290
|
},
|
|
1271
1291
|
)
|
|
1272
1292
|
|
|
1273
|
-
this.pendingMatches = matches
|
|
1274
|
-
|
|
1275
1293
|
const previousMatches = this.state.matches
|
|
1276
1294
|
|
|
1277
1295
|
// Ingest the new matches
|
|
1278
|
-
this.setState((s) => ({
|
|
1296
|
+
this.__store.setState((s) => ({
|
|
1279
1297
|
...s,
|
|
1280
|
-
|
|
1298
|
+
isLoading: true,
|
|
1281
1299
|
location: next,
|
|
1282
|
-
|
|
1300
|
+
pendingMatches,
|
|
1283
1301
|
}))
|
|
1284
1302
|
|
|
1285
1303
|
try {
|
|
1286
1304
|
try {
|
|
1287
1305
|
// Load the matches
|
|
1288
1306
|
await this.loadMatches({
|
|
1289
|
-
matches,
|
|
1307
|
+
matches: pendingMatches,
|
|
1290
1308
|
checkLatest: () => this.checkLatest(promise),
|
|
1291
1309
|
invalidate: opts?.invalidate,
|
|
1292
1310
|
})
|
|
@@ -1310,12 +1328,12 @@ export class Router<
|
|
|
1310
1328
|
this.pendingMatches.includes(id),
|
|
1311
1329
|
)
|
|
1312
1330
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1331
|
+
this.__store.setState((s) => ({
|
|
1332
|
+
...s,
|
|
1333
|
+
isLoading: false,
|
|
1334
|
+
matches: pendingMatches,
|
|
1335
|
+
pendingMatches: undefined,
|
|
1336
|
+
}))
|
|
1319
1337
|
|
|
1320
1338
|
//
|
|
1321
1339
|
;(
|
|
@@ -1517,8 +1535,6 @@ export class Router<
|
|
|
1517
1535
|
? this.latestLocation
|
|
1518
1536
|
: this.state.resolvedLocation
|
|
1519
1537
|
|
|
1520
|
-
// const baseLocation = state.resolvedLocation
|
|
1521
|
-
|
|
1522
1538
|
if (!baseLocation) {
|
|
1523
1539
|
return false
|
|
1524
1540
|
}
|
|
@@ -1633,7 +1649,7 @@ export class Router<
|
|
|
1633
1649
|
return match
|
|
1634
1650
|
})
|
|
1635
1651
|
|
|
1636
|
-
this.setState((s) => {
|
|
1652
|
+
this.__store.setState((s) => {
|
|
1637
1653
|
return {
|
|
1638
1654
|
...s,
|
|
1639
1655
|
matches: matches as any,
|
|
@@ -1672,6 +1688,8 @@ export function getInitialRouterState(
|
|
|
1672
1688
|
location: ParsedLocation,
|
|
1673
1689
|
): RouterState<any> {
|
|
1674
1690
|
return {
|
|
1691
|
+
isLoading: false,
|
|
1692
|
+
isTransitioning: false,
|
|
1675
1693
|
status: 'idle',
|
|
1676
1694
|
resolvedLocation: location,
|
|
1677
1695
|
location,
|