@tanstack/react-router 1.12.16 → 1.14.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/dist/cjs/Matches.cjs +49 -16
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +2 -0
- package/dist/cjs/fileRoute.cjs.map +1 -1
- package/dist/cjs/fileRoute.d.cts +3 -1
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/not-found.cjs +46 -0
- package/dist/cjs/not-found.cjs.map +1 -0
- package/dist/cjs/not-found.d.cts +16 -0
- package/dist/cjs/route.cjs +6 -0
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +7 -0
- package/dist/cjs/router.cjs +114 -55
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +23 -5
- package/dist/esm/Matches.d.ts +2 -0
- package/dist/esm/Matches.js +44 -11
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/fileRoute.d.ts +3 -1
- package/dist/esm/fileRoute.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/not-found.d.ts +16 -0
- package/dist/esm/not-found.js +46 -0
- package/dist/esm/not-found.js.map +1 -0
- package/dist/esm/route.d.ts +7 -0
- package/dist/esm/route.js +6 -0
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +23 -5
- package/dist/esm/router.js +75 -16
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/Matches.tsx +52 -5
- package/src/fileRoute.ts +5 -6
- package/src/index.tsx +1 -0
- package/src/not-found.tsx +54 -0
- package/src/route.ts +18 -6
- package/src/router.ts +120 -17
package/dist/esm/router.d.ts
CHANGED
|
@@ -9,11 +9,14 @@ import { AnyRouteMatch, MatchRouteOptions, RouteMatch } from './Matches.js';
|
|
|
9
9
|
import { ParsedLocation } from './location.js';
|
|
10
10
|
import { SearchSerializer, SearchParser } from './searchParams.js';
|
|
11
11
|
import { BuildLocationFn, CommitLocationOptions, InjectedHtmlEntry, NavigateFn } from './RouterProvider.js';
|
|
12
|
+
import { NotFoundError } from './not-found.js';
|
|
12
13
|
import { ResolveRelativePath, ToOptions } from './link.js';
|
|
13
14
|
import { NoInfer } from '@tanstack/react-store';
|
|
14
15
|
declare global {
|
|
15
16
|
interface Window {
|
|
16
|
-
__TSR_DEHYDRATED__?:
|
|
17
|
+
__TSR_DEHYDRATED__?: {
|
|
18
|
+
data: string;
|
|
19
|
+
};
|
|
17
20
|
__TSR_ROUTER_CONTEXT__?: React.Context<Router<any>>;
|
|
18
21
|
}
|
|
19
22
|
}
|
|
@@ -61,8 +64,19 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TDehydrated extends
|
|
|
61
64
|
InnerWrap?: (props: {
|
|
62
65
|
children: any;
|
|
63
66
|
}) => JSX.Element;
|
|
67
|
+
/**
|
|
68
|
+
* @deprecated
|
|
69
|
+
* Use `notFoundComponent` instead.
|
|
70
|
+
* See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info.
|
|
71
|
+
*/
|
|
64
72
|
notFoundRoute?: AnyRoute;
|
|
73
|
+
transformer?: RouterTransformer;
|
|
65
74
|
errorSerializer?: RouterErrorSerializer<TSerializedError>;
|
|
75
|
+
globalNotFound?: RouteComponent;
|
|
76
|
+
}
|
|
77
|
+
export interface RouterTransformer {
|
|
78
|
+
stringify: (obj: unknown) => string;
|
|
79
|
+
parse: (str: string) => unknown;
|
|
66
80
|
}
|
|
67
81
|
export interface RouterErrorSerializer<TSerializedError> {
|
|
68
82
|
serialize: (err: unknown) => TSerializedError;
|
|
@@ -99,12 +113,12 @@ export interface BuildNextOptions {
|
|
|
99
113
|
export interface DehydratedRouterState {
|
|
100
114
|
dehydratedMatches: DehydratedRouteMatch[];
|
|
101
115
|
}
|
|
102
|
-
export type DehydratedRouteMatch = Pick<RouteMatch, 'id' | 'status' | 'updatedAt' | 'loaderData'>;
|
|
116
|
+
export type DehydratedRouteMatch = Pick<RouteMatch, 'id' | 'status' | 'updatedAt' | 'notFoundError' | 'loaderData'>;
|
|
103
117
|
export interface DehydratedRouter {
|
|
104
118
|
state: DehydratedRouterState;
|
|
105
119
|
}
|
|
106
120
|
export type RouterConstructorOptions<TRouteTree extends AnyRoute, TDehydrated extends Record<string, any>, TSerializedError extends Record<string, any>> = Omit<RouterOptions<TRouteTree, TDehydrated, TSerializedError>, 'context'> & RouterContextOptions<TRouteTree>;
|
|
107
|
-
export declare const componentTypes: readonly ["component", "errorComponent", "pendingComponent"];
|
|
121
|
+
export declare const componentTypes: readonly ["component", "errorComponent", "pendingComponent", "notFoundComponent"];
|
|
108
122
|
export type RouterEvents = {
|
|
109
123
|
onBeforeLoad: {
|
|
110
124
|
type: 'onBeforeLoad';
|
|
@@ -143,7 +157,9 @@ export declare class Router<TRouteTree extends AnyRoute = AnyRoute, TDehydrated
|
|
|
143
157
|
injectedHtml: InjectedHtmlEntry[];
|
|
144
158
|
dehydratedData?: TDehydrated;
|
|
145
159
|
__store: Store<RouterState<TRouteTree>>;
|
|
146
|
-
options: PickAsRequired<RouterOptions<TRouteTree, TDehydrated, TSerializedError>, '
|
|
160
|
+
options: PickAsRequired<Omit<RouterOptions<TRouteTree, TDehydrated, TSerializedError>, 'transformer'> & {
|
|
161
|
+
transformer: RouterTransformer;
|
|
162
|
+
}, 'stringifySearch' | 'parseSearch' | 'context'>;
|
|
147
163
|
history: RouterHistory;
|
|
148
164
|
latestLocation: ParsedLocation;
|
|
149
165
|
basepath: string;
|
|
@@ -186,7 +202,9 @@ export declare class Router<TRouteTree extends AnyRoute = AnyRoute, TDehydrated
|
|
|
186
202
|
dehydrateData: <T>(key: any, getData: T | (() => T | Promise<T>)) => () => T | undefined;
|
|
187
203
|
hydrateData: <T extends unknown = unknown>(key: any) => T | undefined;
|
|
188
204
|
dehydrate: () => DehydratedRouter;
|
|
189
|
-
hydrate: (__do_not_use_server_ctx?:
|
|
205
|
+
hydrate: (__do_not_use_server_ctx?: string) => Promise<void>;
|
|
206
|
+
updateMatchesWithNotFound: (matches: AnyRouteMatch[], currentMatch: AnyRouteMatch, err: NotFoundError) => void;
|
|
207
|
+
hasNotFoundMatch: () => boolean;
|
|
190
208
|
}
|
|
191
209
|
export declare function lazyFn<T extends Record<string, (...args: any[]) => any>, TKey extends keyof T = 'default'>(fn: () => Promise<T>, key?: TKey): (...args: Parameters<T[TKey]>) => Promise<Awaited<ReturnType<T[TKey]>>>;
|
|
192
210
|
export declare class SearchParamError extends Error {
|
package/dist/esm/router.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { createBrowserHistory, createMemoryHistory } from "@tanstack/history";
|
|
2
2
|
import { Store } from "@tanstack/react-store";
|
|
3
|
+
import { rootRouteId } from "./route.js";
|
|
3
4
|
import { defaultStringifySearch, defaultParseSearch } from "./searchParams.js";
|
|
4
5
|
import { replaceEqualDeep, pick, deepEqual, escapeJSON, last, functionalUpdate } from "./utils.js";
|
|
5
6
|
import { getRouteMatch } from "./RouterProvider.js";
|
|
6
7
|
import { trimPath, trimPathLeft, parsePathname, resolvePath, cleanPath, matchPathname, trimPathRight, interpolatePath, joinPaths } from "./path.js";
|
|
7
8
|
import invariant from "tiny-invariant";
|
|
8
9
|
import { isRedirect } from "./redirects.js";
|
|
10
|
+
import { isNotFound } from "./not-found.js";
|
|
9
11
|
const componentTypes = [
|
|
10
12
|
"component",
|
|
11
13
|
"errorComponent",
|
|
12
|
-
"pendingComponent"
|
|
14
|
+
"pendingComponent",
|
|
15
|
+
"notFoundComponent"
|
|
13
16
|
];
|
|
14
17
|
function createRouter(options) {
|
|
15
18
|
return new Router(options);
|
|
@@ -26,6 +29,11 @@ class Router {
|
|
|
26
29
|
this.injectedHtml = [];
|
|
27
30
|
this.startReactTransition = (fn) => fn();
|
|
28
31
|
this.update = (newOptions) => {
|
|
32
|
+
if (newOptions.notFoundRoute) {
|
|
33
|
+
console.warn(
|
|
34
|
+
"The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info."
|
|
35
|
+
);
|
|
36
|
+
}
|
|
29
37
|
const previousOptions = this.options;
|
|
30
38
|
this.options = {
|
|
31
39
|
...this.options,
|
|
@@ -209,15 +217,19 @@ class Router {
|
|
|
209
217
|
});
|
|
210
218
|
let routeCursor = foundRoute || this.routesById["__root__"];
|
|
211
219
|
let matchedRoutes = [routeCursor];
|
|
220
|
+
let isGlobalNotFound = false;
|
|
212
221
|
if (
|
|
213
222
|
// If we found a route, and it's not an index route and we have left over path
|
|
214
|
-
|
|
223
|
+
foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
|
|
215
224
|
// Or if we didn't find a route and we have left over path
|
|
216
225
|
trimPathRight(pathname)
|
|
217
|
-
)
|
|
218
|
-
this.options.notFoundRoute
|
|
226
|
+
)
|
|
219
227
|
) {
|
|
220
|
-
|
|
228
|
+
if (this.options.notFoundRoute) {
|
|
229
|
+
matchedRoutes.push(this.options.notFoundRoute);
|
|
230
|
+
} else {
|
|
231
|
+
isGlobalNotFound = true;
|
|
232
|
+
}
|
|
221
233
|
}
|
|
222
234
|
while (routeCursor == null ? void 0 : routeCursor.parentRoute) {
|
|
223
235
|
routeCursor = routeCursor.parentRoute;
|
|
@@ -287,7 +299,11 @@ class Router {
|
|
|
287
299
|
var _a2;
|
|
288
300
|
return (_a2 = route.options[d]) == null ? void 0 : _a2.preload;
|
|
289
301
|
}));
|
|
290
|
-
const match = existingMatch ? {
|
|
302
|
+
const match = existingMatch ? {
|
|
303
|
+
...existingMatch,
|
|
304
|
+
cause,
|
|
305
|
+
notFoundError: isGlobalNotFound && route.id === rootRouteId ? { global: true } : void 0
|
|
306
|
+
} : {
|
|
291
307
|
id: matchId,
|
|
292
308
|
routeId: route.id,
|
|
293
309
|
params: routeParams,
|
|
@@ -309,6 +325,7 @@ class Router {
|
|
|
309
325
|
loaderDeps,
|
|
310
326
|
invalid: false,
|
|
311
327
|
preload: false,
|
|
328
|
+
notFoundError: isGlobalNotFound && route.id === rootRouteId ? { global: true } : void 0,
|
|
312
329
|
links: (_d = (_c = route.options).links) == null ? void 0 : _d.call(_c),
|
|
313
330
|
scripts: (_f = (_e = route.options).scripts) == null ? void 0 : _f.call(_e),
|
|
314
331
|
staticData: route.options.staticData || {}
|
|
@@ -546,6 +563,9 @@ class Router {
|
|
|
546
563
|
if (isRedirect(err)) {
|
|
547
564
|
throw err;
|
|
548
565
|
}
|
|
566
|
+
if (isNotFound(err)) {
|
|
567
|
+
this.updateMatchesWithNotFound(matches, match, err);
|
|
568
|
+
}
|
|
549
569
|
try {
|
|
550
570
|
(_b2 = (_a2 = route.options).onError) == null ? void 0 : _b2.call(_a2, err);
|
|
551
571
|
} catch (errorHandlerErr) {
|
|
@@ -629,6 +649,9 @@ class Router {
|
|
|
629
649
|
}
|
|
630
650
|
return true;
|
|
631
651
|
}
|
|
652
|
+
if (isNotFound(err)) {
|
|
653
|
+
this.updateMatchesWithNotFound(matches, match, err);
|
|
654
|
+
}
|
|
632
655
|
return false;
|
|
633
656
|
};
|
|
634
657
|
let loadPromise;
|
|
@@ -964,7 +987,7 @@ class Router {
|
|
|
964
987
|
const data = typeof getData === "function" ? await getData() : getData;
|
|
965
988
|
return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(
|
|
966
989
|
strKey
|
|
967
|
-
)}"] = ${JSON.stringify(data)}
|
|
990
|
+
)}"] = ${JSON.stringify(this.options.transformer.stringify(data))}
|
|
968
991
|
;(() => {
|
|
969
992
|
var el = document.getElementById('${id}')
|
|
970
993
|
el.parentElement.removeChild(el)
|
|
@@ -978,7 +1001,9 @@ class Router {
|
|
|
978
1001
|
this.hydrateData = (key) => {
|
|
979
1002
|
if (typeof document !== "undefined") {
|
|
980
1003
|
const strKey = typeof key === "string" ? key : JSON.stringify(key);
|
|
981
|
-
return
|
|
1004
|
+
return this.options.transformer.parse(
|
|
1005
|
+
window[`__TSR_DEHYDRATED__${strKey}`]
|
|
1006
|
+
);
|
|
982
1007
|
}
|
|
983
1008
|
return void 0;
|
|
984
1009
|
};
|
|
@@ -988,7 +1013,15 @@ class Router {
|
|
|
988
1013
|
return {
|
|
989
1014
|
state: {
|
|
990
1015
|
dehydratedMatches: this.state.matches.map((d) => ({
|
|
991
|
-
...pick(d, [
|
|
1016
|
+
...pick(d, [
|
|
1017
|
+
"id",
|
|
1018
|
+
"status",
|
|
1019
|
+
"updatedAt",
|
|
1020
|
+
"loaderData",
|
|
1021
|
+
// Not-founds that occur during SSR don't require the client to load data before
|
|
1022
|
+
// triggering in order to prevent the flicker of the loading component
|
|
1023
|
+
"notFoundError"
|
|
1024
|
+
]),
|
|
992
1025
|
// If an error occurs server-side during SSRing,
|
|
993
1026
|
// send a small subset of the error to the client
|
|
994
1027
|
error: d.error ? {
|
|
@@ -1000,24 +1033,24 @@ class Router {
|
|
|
1000
1033
|
};
|
|
1001
1034
|
};
|
|
1002
1035
|
this.hydrate = async (__do_not_use_server_ctx) => {
|
|
1003
|
-
var _a, _b;
|
|
1036
|
+
var _a, _b, _c;
|
|
1004
1037
|
let _ctx = __do_not_use_server_ctx;
|
|
1005
1038
|
if (typeof document !== "undefined") {
|
|
1006
|
-
_ctx = window.__TSR_DEHYDRATED__;
|
|
1039
|
+
_ctx = (_a = window.__TSR_DEHYDRATED__) == null ? void 0 : _a.data;
|
|
1007
1040
|
}
|
|
1008
1041
|
invariant(
|
|
1009
1042
|
_ctx,
|
|
1010
1043
|
"Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?"
|
|
1011
1044
|
);
|
|
1012
|
-
const ctx = _ctx;
|
|
1045
|
+
const ctx = this.options.transformer.parse(_ctx);
|
|
1013
1046
|
this.dehydratedData = ctx.payload;
|
|
1014
|
-
(
|
|
1047
|
+
(_c = (_b = this.options).hydrate) == null ? void 0 : _c.call(_b, ctx.payload);
|
|
1015
1048
|
const dehydratedState = ctx.router.state;
|
|
1016
1049
|
let matches = this.matchRoutes(
|
|
1017
1050
|
this.state.location.pathname,
|
|
1018
1051
|
this.state.location.search
|
|
1019
1052
|
).map((match) => {
|
|
1020
|
-
var _a2, _b2,
|
|
1053
|
+
var _a2, _b2, _c2, _d, _e, _f;
|
|
1021
1054
|
const dehydratedMatch = dehydratedState.dehydratedMatches.find(
|
|
1022
1055
|
(d) => d.id === match.id
|
|
1023
1056
|
);
|
|
@@ -1033,7 +1066,7 @@ class Router {
|
|
|
1033
1066
|
meta: (_b2 = (_a2 = route.options).meta) == null ? void 0 : _b2.call(_a2, {
|
|
1034
1067
|
loaderData: dehydratedMatch.loaderData
|
|
1035
1068
|
}),
|
|
1036
|
-
links: (_d = (
|
|
1069
|
+
links: (_d = (_c2 = route.options).links) == null ? void 0 : _d.call(_c2),
|
|
1037
1070
|
scripts: (_f = (_e = route.options).scripts) == null ? void 0 : _f.call(_e)
|
|
1038
1071
|
};
|
|
1039
1072
|
}
|
|
@@ -1047,6 +1080,31 @@ class Router {
|
|
|
1047
1080
|
};
|
|
1048
1081
|
});
|
|
1049
1082
|
};
|
|
1083
|
+
this.updateMatchesWithNotFound = (matches, currentMatch, err) => {
|
|
1084
|
+
const matchesByRouteId = Object.fromEntries(
|
|
1085
|
+
matches.map((match) => [match.routeId, match])
|
|
1086
|
+
);
|
|
1087
|
+
if (err.global) {
|
|
1088
|
+
matchesByRouteId[rootRouteId].notFoundError = err;
|
|
1089
|
+
} else {
|
|
1090
|
+
let currentRoute = this.routesById[err.route ?? currentMatch.routeId];
|
|
1091
|
+
while (!currentRoute.options.notFoundComponent) {
|
|
1092
|
+
currentRoute = currentRoute == null ? void 0 : currentRoute.parentRoute;
|
|
1093
|
+
invariant(
|
|
1094
|
+
currentRoute,
|
|
1095
|
+
"Found invalid route tree while trying to find not-found handler."
|
|
1096
|
+
);
|
|
1097
|
+
if (currentRoute.id === rootRouteId)
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
const match = matchesByRouteId[currentRoute.id];
|
|
1101
|
+
invariant(match, "Could not find match for route: " + currentRoute.id);
|
|
1102
|
+
match.notFoundError = err;
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
this.hasNotFoundMatch = () => {
|
|
1106
|
+
return this.__store.state.matches.some((d) => d.notFoundError);
|
|
1107
|
+
};
|
|
1050
1108
|
this.update({
|
|
1051
1109
|
defaultPreloadDelay: 50,
|
|
1052
1110
|
defaultPendingMs: 1e3,
|
|
@@ -1054,7 +1112,8 @@ class Router {
|
|
|
1054
1112
|
context: void 0,
|
|
1055
1113
|
...options,
|
|
1056
1114
|
stringifySearch: (options == null ? void 0 : options.stringifySearch) ?? defaultStringifySearch,
|
|
1057
|
-
parseSearch: (options == null ? void 0 : options.parseSearch) ?? defaultParseSearch
|
|
1115
|
+
parseSearch: (options == null ? void 0 : options.parseSearch) ?? defaultParseSearch,
|
|
1116
|
+
transformer: (options == null ? void 0 : options.transformer) ?? JSON
|
|
1058
1117
|
});
|
|
1059
1118
|
}
|
|
1060
1119
|
get state() {
|