@tanstack/react-router 0.0.1-beta.226 → 0.0.1-beta.228
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 +4 -6
- package/build/cjs/Matches.js.map +1 -1
- package/build/cjs/RouterProvider.js +0 -25
- package/build/cjs/RouterProvider.js.map +1 -1
- package/build/cjs/fileRoute.js.map +1 -1
- package/build/cjs/route.js.map +1 -1
- package/build/cjs/router.js +45 -50
- package/build/cjs/router.js.map +1 -1
- package/build/esm/index.js +49 -81
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +287 -287
- package/build/types/Matches.d.ts +0 -1
- package/build/types/fileRoute.d.ts +2 -2
- package/build/types/route.d.ts +6 -2
- package/build/umd/index.development.js +49 -82
- package/build/umd/index.development.js.map +1 -1
- package/build/umd/index.production.js +1 -1
- package/build/umd/index.production.js.map +1 -1
- package/package.json +2 -2
- package/src/Matches.tsx +17 -23
- package/src/RouterProvider.tsx +4 -71
- package/src/fileRoute.ts +7 -6
- package/src/route.ts +3 -2
- package/src/router.ts +75 -75
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.228",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "tanstack/router",
|
|
7
7
|
"homepage": "https://tanstack.com/router",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@babel/runtime": "^7.16.7",
|
|
43
43
|
"tiny-invariant": "^1.3.1",
|
|
44
44
|
"tiny-warning": "^1.0.3",
|
|
45
|
-
"@tanstack/history": "0.0.1-beta.
|
|
45
|
+
"@tanstack/history": "0.0.1-beta.228"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "rollup --config rollup.config.js"
|
package/src/Matches.tsx
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
RoutePaths,
|
|
15
15
|
} from './routeInfo'
|
|
16
16
|
import { RegisteredRouter } from './router'
|
|
17
|
-
import { NoInfer, StrictOrFrom } from './utils'
|
|
17
|
+
import { NoInfer, StrictOrFrom, pick } from './utils'
|
|
18
18
|
|
|
19
19
|
export interface RouteMatch<
|
|
20
20
|
TRouteTree extends AnyRoute = AnyRoute,
|
|
@@ -35,7 +35,6 @@ export interface RouteMatch<
|
|
|
35
35
|
loaderData?: RouteById<TRouteTree, TRouteId>['types']['loaderData']
|
|
36
36
|
__resolveLoadPromise?: () => void
|
|
37
37
|
context: RouteById<TRouteTree, TRouteId>['types']['allContext']
|
|
38
|
-
routeSearch: RouteById<TRouteTree, TRouteId>['types']['searchSchema']
|
|
39
38
|
search: FullSearchSchema<TRouteTree> &
|
|
40
39
|
RouteById<TRouteTree, TRouteId>['types']['fullSearchSchema']
|
|
41
40
|
fetchedAt: number
|
|
@@ -85,7 +84,6 @@ export function Matches() {
|
|
|
85
84
|
)
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
const defaultPending = () => null
|
|
89
87
|
function SafeFragment(props: any) {
|
|
90
88
|
return <>{props.children}</>
|
|
91
89
|
}
|
|
@@ -98,17 +96,17 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
|
|
|
98
96
|
const locationKey = useRouterState().location.state?.key
|
|
99
97
|
|
|
100
98
|
const PendingComponent = (route.options.pendingComponent ??
|
|
101
|
-
options.defaultPendingComponent
|
|
102
|
-
defaultPending) as any
|
|
99
|
+
options.defaultPendingComponent) as any
|
|
103
100
|
|
|
104
101
|
const routeErrorComponent =
|
|
105
102
|
route.options.errorComponent ??
|
|
106
103
|
options.defaultErrorComponent ??
|
|
107
104
|
ErrorComponent
|
|
108
105
|
|
|
109
|
-
const ResolvedSuspenseBoundary =
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
const ResolvedSuspenseBoundary =
|
|
107
|
+
route.options.wrapInSuspense ?? PendingComponent
|
|
108
|
+
? React.Suspense
|
|
109
|
+
: SafeFragment
|
|
112
110
|
|
|
113
111
|
const errorComponent = routeErrorComponent
|
|
114
112
|
? React.useCallback(
|
|
@@ -125,6 +123,8 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
|
|
|
125
123
|
)
|
|
126
124
|
: undefined
|
|
127
125
|
|
|
126
|
+
const ResolvedCatchBoundary = errorComponent ? CatchBoundary : SafeFragment
|
|
127
|
+
|
|
128
128
|
return (
|
|
129
129
|
<matchesContext.Provider value={matches}>
|
|
130
130
|
<ResolvedSuspenseBoundary
|
|
@@ -135,21 +135,15 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
|
|
|
135
135
|
useParams: route.useParams,
|
|
136
136
|
})}
|
|
137
137
|
>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
</CatchBoundary>
|
|
148
|
-
) : (
|
|
149
|
-
<SafeFragment>
|
|
150
|
-
<MatchInner match={match} />
|
|
151
|
-
</SafeFragment>
|
|
152
|
-
)}
|
|
138
|
+
<ResolvedCatchBoundary
|
|
139
|
+
resetKey={locationKey}
|
|
140
|
+
errorComponent={errorComponent}
|
|
141
|
+
onCatch={() => {
|
|
142
|
+
warning(false, `Error in route match: ${match.id}`)
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<MatchInner match={match} />
|
|
146
|
+
</ResolvedCatchBoundary>
|
|
153
147
|
</ResolvedSuspenseBoundary>
|
|
154
148
|
</matchesContext.Provider>
|
|
155
149
|
)
|
package/src/RouterProvider.tsx
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
HistoryLocation,
|
|
3
|
-
HistoryState,
|
|
4
|
-
RouterHistory,
|
|
5
|
-
createBrowserHistory,
|
|
6
|
-
} from '@tanstack/history'
|
|
7
1
|
import * as React from 'react'
|
|
8
|
-
import invariant from 'tiny-invariant'
|
|
9
2
|
import warning from 'tiny-warning'
|
|
10
3
|
import { Matches } from './Matches'
|
|
11
4
|
import {
|
|
@@ -16,53 +9,18 @@ import {
|
|
|
16
9
|
ToOptions,
|
|
17
10
|
} from './link'
|
|
18
11
|
import { ParsedLocation } from './location'
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
interpolatePath,
|
|
22
|
-
joinPaths,
|
|
23
|
-
matchPathname,
|
|
24
|
-
parsePathname,
|
|
25
|
-
resolvePath,
|
|
26
|
-
trimPath,
|
|
27
|
-
trimPathRight,
|
|
28
|
-
} from './path'
|
|
29
|
-
import { isRedirect } from './redirects'
|
|
30
|
-
import {
|
|
31
|
-
AnyPathParams,
|
|
32
|
-
AnyRoute,
|
|
33
|
-
AnySearchSchema,
|
|
34
|
-
LoaderFnContext,
|
|
35
|
-
Route,
|
|
36
|
-
} from './route'
|
|
37
|
-
import {
|
|
38
|
-
FullSearchSchema,
|
|
39
|
-
RouteById,
|
|
40
|
-
RoutePaths,
|
|
41
|
-
RoutesById,
|
|
42
|
-
RoutesByPath,
|
|
43
|
-
} from './routeInfo'
|
|
12
|
+
import { AnyRoute } from './route'
|
|
13
|
+
import { RouteById, RoutePaths } from './routeInfo'
|
|
44
14
|
import {
|
|
45
15
|
BuildNextOptions,
|
|
46
|
-
DehydratedRouteMatch,
|
|
47
16
|
RegisteredRouter,
|
|
48
17
|
Router,
|
|
49
18
|
RouterOptions,
|
|
50
19
|
RouterState,
|
|
51
|
-
componentTypes,
|
|
52
20
|
} from './router'
|
|
53
|
-
import {
|
|
54
|
-
NoInfer,
|
|
55
|
-
PickAsRequired,
|
|
56
|
-
functionalUpdate,
|
|
57
|
-
last,
|
|
58
|
-
deepEqual,
|
|
59
|
-
pick,
|
|
60
|
-
replaceEqualDeep,
|
|
61
|
-
useStableCallback,
|
|
62
|
-
escapeJSON,
|
|
63
|
-
} from './utils'
|
|
21
|
+
import { NoInfer, PickAsRequired } from './utils'
|
|
64
22
|
import { MatchRouteOptions } from './Matches'
|
|
65
|
-
import {
|
|
23
|
+
import { RouteMatch } from './Matches'
|
|
66
24
|
|
|
67
25
|
export interface CommitLocationOptions {
|
|
68
26
|
replace?: boolean
|
|
@@ -108,31 +66,6 @@ export type BuildLocationFn<TRouteTree extends AnyRoute> = (
|
|
|
108
66
|
|
|
109
67
|
export type InjectedHtmlEntry = string | (() => Promise<string> | string)
|
|
110
68
|
|
|
111
|
-
// export type RouterContext<
|
|
112
|
-
// TRouteTree extends AnyRoute,
|
|
113
|
-
// // TDehydrated extends Record<string, any>,
|
|
114
|
-
// > = {
|
|
115
|
-
// buildLink: BuildLinkFn<TRouteTree>
|
|
116
|
-
// state: RouterState<TRouteTree>
|
|
117
|
-
// navigate: NavigateFn<TRouteTree>
|
|
118
|
-
// matchRoute: MatchRouteFn<TRouteTree>
|
|
119
|
-
// routeTree: TRouteTree
|
|
120
|
-
// routesById: RoutesById<TRouteTree>
|
|
121
|
-
// options: RouterOptions<TRouteTree>
|
|
122
|
-
// history: RouterHistory
|
|
123
|
-
// load: LoadFn
|
|
124
|
-
// buildLocation: BuildLocationFn<TRouteTree>
|
|
125
|
-
// subscribe: Router<TRouteTree>['subscribe']
|
|
126
|
-
// resetNextScrollRef: React.MutableRefObject<boolean>
|
|
127
|
-
// injectedHtmlRef: React.MutableRefObject<InjectedHtmlEntry[]>
|
|
128
|
-
// injectHtml: (entry: InjectedHtmlEntry) => void
|
|
129
|
-
// dehydrateData: <T>(
|
|
130
|
-
// key: any,
|
|
131
|
-
// getData: T | (() => Promise<T> | T),
|
|
132
|
-
// ) => () => void
|
|
133
|
-
// hydrateData: <T>(key: any) => T | undefined
|
|
134
|
-
// }
|
|
135
|
-
|
|
136
69
|
export const routerContext = React.createContext<Router<any>>(null!)
|
|
137
70
|
|
|
138
71
|
if (typeof document !== 'undefined') {
|
package/src/fileRoute.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
TrimPathLeft,
|
|
15
15
|
RouteConstraints,
|
|
16
16
|
} from './route'
|
|
17
|
-
import { Assign,
|
|
17
|
+
import { Assign, Expand, IsAny } from './utils'
|
|
18
18
|
|
|
19
19
|
export interface FileRoutesByPath {
|
|
20
20
|
// '/': {
|
|
@@ -86,13 +86,14 @@ export class FileRoute<
|
|
|
86
86
|
|
|
87
87
|
createRoute = <
|
|
88
88
|
TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
|
|
89
|
-
TFullSearchSchema extends
|
|
89
|
+
TFullSearchSchema extends
|
|
90
|
+
RouteConstraints['TFullSearchSchema'] = ResolveFullSearchSchema<
|
|
90
91
|
TParentRoute,
|
|
91
92
|
TSearchSchema
|
|
92
93
|
>,
|
|
93
|
-
TParams extends RouteConstraints['TParams'] =
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
TParams extends RouteConstraints['TParams'] = Expand<
|
|
95
|
+
Record<ParsePathParams<TPath>, string>
|
|
96
|
+
>,
|
|
96
97
|
TAllParams extends RouteConstraints['TAllParams'] = MergeFromFromParent<
|
|
97
98
|
TParentRoute['types']['allParams'],
|
|
98
99
|
TParams
|
|
@@ -112,7 +113,7 @@ export class FileRoute<
|
|
|
112
113
|
RouteOptions<
|
|
113
114
|
TParentRoute,
|
|
114
115
|
string,
|
|
115
|
-
|
|
116
|
+
TPath,
|
|
116
117
|
TSearchSchema,
|
|
117
118
|
TFullSearchSchema,
|
|
118
119
|
TParams,
|
package/src/route.ts
CHANGED
|
@@ -137,7 +137,8 @@ export type BaseRouteOptions<
|
|
|
137
137
|
TRouteContext
|
|
138
138
|
>
|
|
139
139
|
}) & {
|
|
140
|
-
|
|
140
|
+
key?: (opts: { search: TFullSearchSchema; location: ParsedLocation }) => any
|
|
141
|
+
loader?: RouteLoaderFn<
|
|
141
142
|
TAllParams,
|
|
142
143
|
TFullSearchSchema,
|
|
143
144
|
NoInfer<TAllContext>,
|
|
@@ -265,7 +266,7 @@ export type ParentParams<TParentParams> = AnyPathParams extends TParentParams
|
|
|
265
266
|
[Key in keyof TParentParams]?: DefinedPathParamWarning
|
|
266
267
|
}
|
|
267
268
|
|
|
268
|
-
export type
|
|
269
|
+
export type RouteLoaderFn<
|
|
269
270
|
TAllParams = {},
|
|
270
271
|
TFullSearchSchema extends Record<string, any> = {},
|
|
271
272
|
TAllContext extends Record<string, any> = AnyContext,
|
package/src/router.ts
CHANGED
|
@@ -553,67 +553,21 @@ export class Router<
|
|
|
553
553
|
return
|
|
554
554
|
})
|
|
555
555
|
|
|
556
|
-
const matches =
|
|
557
|
-
const interpolatedPath = interpolatePath(route.path, routeParams)
|
|
558
|
-
const matchId = interpolatePath(route.id, routeParams, true)
|
|
556
|
+
const matches: AnyRouteMatch[] = []
|
|
559
557
|
|
|
560
|
-
|
|
561
|
-
//
|
|
562
|
-
//
|
|
563
|
-
|
|
558
|
+
matchedRoutes.forEach((route, index) => {
|
|
559
|
+
// Take each matched route and resolve + validate its search params
|
|
560
|
+
// This has to happen serially because each route's search params
|
|
561
|
+
// can depend on the parent route's search params
|
|
562
|
+
// It must also happen before we create the match so that we can
|
|
563
|
+
// pass the search params to the route's potential key function
|
|
564
|
+
// which is used to uniquely identify the route match in state
|
|
564
565
|
|
|
565
|
-
const
|
|
566
|
-
? 'stay'
|
|
567
|
-
: 'enter'
|
|
568
|
-
|
|
569
|
-
if (existingMatch) {
|
|
570
|
-
return { ...existingMatch, cause }
|
|
571
|
-
}
|
|
566
|
+
const parentMatch = matches[index - 1]
|
|
572
567
|
|
|
573
|
-
|
|
574
|
-
const hasLoaders = !!(
|
|
575
|
-
route.options.loader ||
|
|
576
|
-
componentTypes.some((d) => (route.options[d] as any)?.preload)
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
const routeMatch: AnyRouteMatch = {
|
|
580
|
-
id: matchId,
|
|
581
|
-
routeId: route.id,
|
|
582
|
-
params: routeParams,
|
|
583
|
-
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
584
|
-
updatedAt: Date.now(),
|
|
585
|
-
routeSearch: {},
|
|
586
|
-
search: {} as any,
|
|
587
|
-
status: hasLoaders ? 'pending' : 'success',
|
|
588
|
-
isFetching: false,
|
|
589
|
-
invalid: false,
|
|
590
|
-
error: undefined,
|
|
591
|
-
paramsError: parseErrors[index],
|
|
592
|
-
searchError: undefined,
|
|
593
|
-
loadPromise: Promise.resolve(),
|
|
594
|
-
context: undefined!,
|
|
595
|
-
abortController: new AbortController(),
|
|
596
|
-
shouldReloadDeps: undefined,
|
|
597
|
-
fetchedAt: 0,
|
|
598
|
-
cause,
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
return routeMatch
|
|
602
|
-
})
|
|
603
|
-
|
|
604
|
-
// Take each match and resolve its search params and context
|
|
605
|
-
// This has to happen after the matches are created or found
|
|
606
|
-
// so that we can use the parent match's search params and context
|
|
607
|
-
matches.forEach((match, i): any => {
|
|
608
|
-
const parentMatch = matches[i - 1]
|
|
609
|
-
const route = this.looseRoutesById[match.routeId]!
|
|
610
|
-
|
|
611
|
-
const searchInfo = (() => {
|
|
568
|
+
const [preMatchSearch, searchError]: [Record<string, any>, any] = (() => {
|
|
612
569
|
// Validate the search params and stabilize them
|
|
613
|
-
const
|
|
614
|
-
search: parentMatch?.search ?? locationSearch,
|
|
615
|
-
routeSearch: parentMatch?.routeSearch ?? locationSearch,
|
|
616
|
-
}
|
|
570
|
+
const parentSearch = parentMatch?.search ?? locationSearch
|
|
617
571
|
|
|
618
572
|
try {
|
|
619
573
|
const validator =
|
|
@@ -621,35 +575,81 @@ export class Router<
|
|
|
621
575
|
? route.options.validateSearch.parse
|
|
622
576
|
: route.options.validateSearch
|
|
623
577
|
|
|
624
|
-
let
|
|
625
|
-
|
|
626
|
-
let search = {
|
|
627
|
-
...parentSearchInfo.search,
|
|
628
|
-
...routeSearch,
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
routeSearch = replaceEqualDeep(match.routeSearch, routeSearch)
|
|
632
|
-
search = replaceEqualDeep(match.search, search)
|
|
578
|
+
let search = validator?.(parentSearch) ?? {}
|
|
633
579
|
|
|
634
|
-
return
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
580
|
+
return [
|
|
581
|
+
{
|
|
582
|
+
...parentSearch,
|
|
583
|
+
...search,
|
|
584
|
+
},
|
|
585
|
+
undefined,
|
|
586
|
+
]
|
|
639
587
|
} catch (err: any) {
|
|
640
|
-
|
|
588
|
+
const searchError = new SearchParamError(err.message, {
|
|
641
589
|
cause: err,
|
|
642
590
|
})
|
|
643
591
|
|
|
644
592
|
if (opts?.throwOnError) {
|
|
645
|
-
throw
|
|
593
|
+
throw searchError
|
|
646
594
|
}
|
|
647
595
|
|
|
648
|
-
return
|
|
596
|
+
return [parentSearch, searchError]
|
|
649
597
|
}
|
|
650
598
|
})()
|
|
651
599
|
|
|
652
|
-
|
|
600
|
+
const interpolatedPath = interpolatePath(route.path, routeParams)
|
|
601
|
+
const matchId =
|
|
602
|
+
interpolatePath(route.id, routeParams, true) +
|
|
603
|
+
(route.options.key?.({
|
|
604
|
+
search: preMatchSearch,
|
|
605
|
+
location: this.state.location,
|
|
606
|
+
}) ?? '')
|
|
607
|
+
|
|
608
|
+
// Waste not, want not. If we already have a match for this route,
|
|
609
|
+
// reuse it. This is important for layout routes, which might stick
|
|
610
|
+
// around between navigation actions that only change leaf routes.
|
|
611
|
+
const existingMatch = getRouteMatch(this.state, matchId)
|
|
612
|
+
|
|
613
|
+
const cause = this.state.matches.find((d) => d.id === matchId)
|
|
614
|
+
? 'stay'
|
|
615
|
+
: 'enter'
|
|
616
|
+
|
|
617
|
+
// Create a fresh route match
|
|
618
|
+
const hasLoaders = !!(
|
|
619
|
+
route.options.loader ||
|
|
620
|
+
componentTypes.some((d) => (route.options[d] as any)?.preload)
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
const match: AnyRouteMatch = existingMatch
|
|
624
|
+
? { ...existingMatch, cause }
|
|
625
|
+
: {
|
|
626
|
+
id: matchId,
|
|
627
|
+
routeId: route.id,
|
|
628
|
+
params: routeParams,
|
|
629
|
+
pathname: joinPaths([this.basepath, interpolatedPath]),
|
|
630
|
+
updatedAt: Date.now(),
|
|
631
|
+
search: {} as any,
|
|
632
|
+
searchError: undefined,
|
|
633
|
+
status: hasLoaders ? 'pending' : 'success',
|
|
634
|
+
isFetching: false,
|
|
635
|
+
invalid: false,
|
|
636
|
+
error: undefined,
|
|
637
|
+
paramsError: parseErrors[index],
|
|
638
|
+
loadPromise: Promise.resolve(),
|
|
639
|
+
context: undefined!,
|
|
640
|
+
abortController: new AbortController(),
|
|
641
|
+
shouldReloadDeps: undefined,
|
|
642
|
+
fetchedAt: 0,
|
|
643
|
+
cause,
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Regardless of whether we're reusing an existing match or creating
|
|
647
|
+
// a new one, we need to update the match's search params
|
|
648
|
+
match.search = replaceEqualDeep(match.search, preMatchSearch)
|
|
649
|
+
// And also update the searchError if there is one
|
|
650
|
+
match.searchError = searchError
|
|
651
|
+
|
|
652
|
+
matches.push(match)
|
|
653
653
|
})
|
|
654
654
|
|
|
655
655
|
return matches as any
|