@tanstack/react-router 1.6.0 → 1.7.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/build/cjs/Matches.js +22 -1
- package/build/cjs/Matches.js.map +1 -1
- package/build/cjs/awaited.js +21 -4
- package/build/cjs/awaited.js.map +1 -1
- package/build/cjs/defer.js +7 -2
- package/build/cjs/defer.js.map +1 -1
- package/build/cjs/index.js +3 -0
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/router.js +20 -1
- package/build/cjs/router.js.map +1 -1
- package/build/esm/index.js +1900 -1846
- package/build/esm/index.js.map +1 -1
- package/build/stats-html.html +1 -1
- package/build/stats-react.json +585 -560
- package/build/types/Matches.d.ts +5 -0
- package/build/types/defer.d.ts +4 -1
- package/build/types/router.d.ts +20 -6
- package/build/types/routerContext.d.ts +1 -1
- package/build/umd/index.development.js +1902 -1845
- 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 +2 -2
- package/src/Matches.tsx +28 -1
- package/src/awaited.tsx +25 -1
- package/src/defer.ts +12 -2
- package/src/router.ts +50 -7
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-router",
|
|
3
3
|
"author": "Tanner Linsley",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.7.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "tanstack/router",
|
|
7
7
|
"homepage": "https://tanstack.com/router",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@tanstack/store": "^0.1.3",
|
|
45
45
|
"tiny-invariant": "^1.3.1",
|
|
46
46
|
"tiny-warning": "^1.0.3",
|
|
47
|
-
"@tanstack/history": "1.
|
|
47
|
+
"@tanstack/history": "1.7.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "rollup --config rollup.config.js"
|
package/src/Matches.tsx
CHANGED
|
@@ -162,7 +162,13 @@ function MatchInner({
|
|
|
162
162
|
})
|
|
163
163
|
|
|
164
164
|
if (match.status === 'error') {
|
|
165
|
-
|
|
165
|
+
if (isServerSideError(match.error)) {
|
|
166
|
+
const deserializeError =
|
|
167
|
+
router.options.errorSerializer?.deserialize ?? defaultDeserializeError
|
|
168
|
+
throw deserializeError(match.error.data)
|
|
169
|
+
} else {
|
|
170
|
+
throw match.error
|
|
171
|
+
}
|
|
166
172
|
}
|
|
167
173
|
|
|
168
174
|
if (match.status === 'pending') {
|
|
@@ -423,3 +429,24 @@ export function useLoaderData<
|
|
|
423
429
|
},
|
|
424
430
|
})
|
|
425
431
|
}
|
|
432
|
+
|
|
433
|
+
export function isServerSideError(error: unknown): error is {
|
|
434
|
+
__isServerError: true
|
|
435
|
+
data: Record<string, any>
|
|
436
|
+
} {
|
|
437
|
+
if (!(typeof error === 'object' && error && 'data' in error)) return false
|
|
438
|
+
if (!('__isServerError' in error && error.__isServerError)) return false
|
|
439
|
+
if (!(typeof error.data === 'object' && error.data)) return false
|
|
440
|
+
|
|
441
|
+
return error.__isServerError === true
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function defaultDeserializeError(serializedData: Record<string, any>) {
|
|
445
|
+
if ('name' in serializedData && 'message' in serializedData) {
|
|
446
|
+
const error = new Error(serializedData.message)
|
|
447
|
+
error.name = serializedData.name
|
|
448
|
+
return error
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return serializedData.data
|
|
452
|
+
}
|
package/src/awaited.tsx
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import warning from 'tiny-warning'
|
|
2
|
+
import { defaultDeserializeError, isServerSideError } from './Matches'
|
|
1
3
|
import { useRouter } from './useRouter'
|
|
2
4
|
import { DeferredPromise, isDehydratedDeferred } from './defer'
|
|
5
|
+
import { defaultSerializeError } from './router'
|
|
3
6
|
|
|
4
7
|
export type AwaitOptions<T> = {
|
|
5
8
|
promise: DeferredPromise<T>
|
|
@@ -13,6 +16,7 @@ export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
|
|
|
13
16
|
|
|
14
17
|
if (isDehydratedDeferred(promise)) {
|
|
15
18
|
state = router.hydrateData(key)!
|
|
19
|
+
if (!state) throw new Error('Could not find dehydrated data')
|
|
16
20
|
promise = Promise.resolve(state.data) as DeferredPromise<any>
|
|
17
21
|
promise.__deferredState = state
|
|
18
22
|
}
|
|
@@ -22,7 +26,27 @@ export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
|
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
if (state.status === 'error') {
|
|
25
|
-
|
|
29
|
+
if (typeof document !== 'undefined') {
|
|
30
|
+
if (isServerSideError(state.error)) {
|
|
31
|
+
throw (
|
|
32
|
+
router.options.errorSerializer?.deserialize ?? defaultDeserializeError
|
|
33
|
+
)(state.error.data as any)
|
|
34
|
+
} else {
|
|
35
|
+
warning(
|
|
36
|
+
false,
|
|
37
|
+
"Encountered a server-side error that doesn't fit the expected shape",
|
|
38
|
+
)
|
|
39
|
+
throw state.error
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
router.dehydrateData(key, state)
|
|
43
|
+
throw {
|
|
44
|
+
data: (
|
|
45
|
+
router.options.errorSerializer?.serialize ?? defaultSerializeError
|
|
46
|
+
)(state.error),
|
|
47
|
+
__isServerError: true,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
26
50
|
}
|
|
27
51
|
|
|
28
52
|
router.dehydrateData(key, state)
|
package/src/defer.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { defaultSerializeError } from './router'
|
|
2
|
+
|
|
1
3
|
export type DeferredPromiseState<T> = { uid: string } & (
|
|
2
4
|
| {
|
|
3
5
|
status: 'pending'
|
|
@@ -19,7 +21,12 @@ export type DeferredPromise<T> = Promise<T> & {
|
|
|
19
21
|
__deferredState: DeferredPromiseState<T>
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
export function defer<T>(
|
|
24
|
+
export function defer<T>(
|
|
25
|
+
_promise: Promise<T>,
|
|
26
|
+
options?: {
|
|
27
|
+
serializeError?: typeof defaultSerializeError
|
|
28
|
+
},
|
|
29
|
+
) {
|
|
23
30
|
const promise = _promise as DeferredPromise<T>
|
|
24
31
|
|
|
25
32
|
if (!promise.__deferredState) {
|
|
@@ -37,7 +44,10 @@ export function defer<T>(_promise: Promise<T>) {
|
|
|
37
44
|
})
|
|
38
45
|
.catch((error) => {
|
|
39
46
|
state.status = 'error' as any
|
|
40
|
-
state.error =
|
|
47
|
+
state.error = {
|
|
48
|
+
data: (options?.serializeError ?? defaultSerializeError)(error),
|
|
49
|
+
__isServerError: true,
|
|
50
|
+
}
|
|
41
51
|
})
|
|
42
52
|
}
|
|
43
53
|
|
package/src/router.ts
CHANGED
|
@@ -105,6 +105,7 @@ export type RouterContextOptions<TRouteTree extends AnyRoute> =
|
|
|
105
105
|
export interface RouterOptions<
|
|
106
106
|
TRouteTree extends AnyRoute,
|
|
107
107
|
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
108
|
+
TSerializedError extends Record<string, any> = Record<string, any>,
|
|
108
109
|
> {
|
|
109
110
|
history?: RouterHistory
|
|
110
111
|
stringifySearch?: SearchSerializer
|
|
@@ -131,6 +132,11 @@ export interface RouterOptions<
|
|
|
131
132
|
Wrap?: (props: { children: any }) => JSX.Element
|
|
132
133
|
InnerWrap?: (props: { children: any }) => JSX.Element
|
|
133
134
|
notFoundRoute?: AnyRoute
|
|
135
|
+
errorSerializer?: RouterErrorSerializer<TSerializedError>
|
|
136
|
+
}
|
|
137
|
+
export interface RouterErrorSerializer<TSerializedError> {
|
|
138
|
+
serialize: (err: unknown) => TSerializedError
|
|
139
|
+
deserialize: (err: TSerializedError) => unknown
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
|
|
@@ -180,7 +186,8 @@ export interface DehydratedRouter {
|
|
|
180
186
|
export type RouterConstructorOptions<
|
|
181
187
|
TRouteTree extends AnyRoute,
|
|
182
188
|
TDehydrated extends Record<string, any>,
|
|
183
|
-
|
|
189
|
+
TSerializedError extends Record<string, any>,
|
|
190
|
+
> = Omit<RouterOptions<TRouteTree, TDehydrated, TSerializedError>, 'context'> &
|
|
184
191
|
RouterContextOptions<TRouteTree>
|
|
185
192
|
|
|
186
193
|
export const componentTypes = [
|
|
@@ -220,6 +227,7 @@ export type RouterListener<TRouterEvent extends RouterEvent> = {
|
|
|
220
227
|
export class Router<
|
|
221
228
|
TRouteTree extends AnyRoute = AnyRoute,
|
|
222
229
|
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
230
|
+
TSerializedError extends Record<string, any> = Record<string, any>,
|
|
223
231
|
> {
|
|
224
232
|
// Option-independent properties
|
|
225
233
|
tempLocationKey: string | undefined = `${Math.round(
|
|
@@ -235,7 +243,7 @@ export class Router<
|
|
|
235
243
|
// Must build in constructor
|
|
236
244
|
__store!: Store<RouterState<TRouteTree>>
|
|
237
245
|
options!: PickAsRequired<
|
|
238
|
-
RouterOptions<TRouteTree, TDehydrated>,
|
|
246
|
+
RouterOptions<TRouteTree, TDehydrated, TSerializedError>,
|
|
239
247
|
'stringifySearch' | 'parseSearch' | 'context'
|
|
240
248
|
>
|
|
241
249
|
history!: RouterHistory
|
|
@@ -246,7 +254,13 @@ export class Router<
|
|
|
246
254
|
routesByPath!: RoutesByPath<TRouteTree>
|
|
247
255
|
flatRoutes!: AnyRoute[]
|
|
248
256
|
|
|
249
|
-
constructor(
|
|
257
|
+
constructor(
|
|
258
|
+
options: RouterConstructorOptions<
|
|
259
|
+
TRouteTree,
|
|
260
|
+
TDehydrated,
|
|
261
|
+
TSerializedError
|
|
262
|
+
>,
|
|
263
|
+
) {
|
|
250
264
|
this.update({
|
|
251
265
|
defaultPreloadDelay: 50,
|
|
252
266
|
defaultPendingMs: 1000,
|
|
@@ -263,7 +277,13 @@ export class Router<
|
|
|
263
277
|
// router can be used in a non-react environment if necessary
|
|
264
278
|
startReactTransition: (fn: () => void) => void = (fn) => fn()
|
|
265
279
|
|
|
266
|
-
update = (
|
|
280
|
+
update = (
|
|
281
|
+
newOptions: RouterConstructorOptions<
|
|
282
|
+
TRouteTree,
|
|
283
|
+
TDehydrated,
|
|
284
|
+
TSerializedError
|
|
285
|
+
>,
|
|
286
|
+
) => {
|
|
267
287
|
const previousOptions = this.options
|
|
268
288
|
this.options = {
|
|
269
289
|
...this.options,
|
|
@@ -1616,11 +1636,22 @@ export class Router<
|
|
|
1616
1636
|
}
|
|
1617
1637
|
|
|
1618
1638
|
dehydrate = (): DehydratedRouter => {
|
|
1639
|
+
const pickError =
|
|
1640
|
+
this.options.errorSerializer?.serialize ?? defaultSerializeError
|
|
1641
|
+
|
|
1619
1642
|
return {
|
|
1620
1643
|
state: {
|
|
1621
|
-
dehydratedMatches: this.state.matches.map((d) =>
|
|
1622
|
-
pick(d, ['id', 'status', 'updatedAt', 'loaderData']),
|
|
1623
|
-
|
|
1644
|
+
dehydratedMatches: this.state.matches.map((d) => ({
|
|
1645
|
+
...pick(d, ['id', 'status', 'updatedAt', 'loaderData']),
|
|
1646
|
+
// If an error occurs server-side during SSRing,
|
|
1647
|
+
// send a small subset of the error to the client
|
|
1648
|
+
error: d.error
|
|
1649
|
+
? {
|
|
1650
|
+
data: pickError(d.error),
|
|
1651
|
+
__isServerError: true,
|
|
1652
|
+
}
|
|
1653
|
+
: undefined,
|
|
1654
|
+
})),
|
|
1624
1655
|
},
|
|
1625
1656
|
}
|
|
1626
1657
|
}
|
|
@@ -1713,3 +1744,15 @@ export function getInitialRouterState(
|
|
|
1713
1744
|
lastUpdated: Date.now(),
|
|
1714
1745
|
}
|
|
1715
1746
|
}
|
|
1747
|
+
|
|
1748
|
+
export function defaultSerializeError(err: unknown) {
|
|
1749
|
+
if (err instanceof Error)
|
|
1750
|
+
return {
|
|
1751
|
+
name: err.name,
|
|
1752
|
+
message: err.message,
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
return {
|
|
1756
|
+
data: err,
|
|
1757
|
+
}
|
|
1758
|
+
}
|