@tanstack/react-router 0.0.1-beta.241 → 0.0.1-beta.242

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/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.241",
4
+ "version": "0.0.1-beta.242",
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": "0.0.1-beta.241"
47
+ "@tanstack/history": "0.0.1-beta.242"
48
48
  },
49
49
  "scripts": {
50
50
  "build": "rollup --config rollup.config.js"
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react'
2
2
 
3
3
  export function CatchBoundary(props: {
4
- resetKey: string
4
+ getResetKey: () => string
5
5
  children: any
6
6
  errorComponent?: any
7
7
  onCatch: (error: any) => void
@@ -10,7 +10,7 @@ export function CatchBoundary(props: {
10
10
 
11
11
  return (
12
12
  <CatchBoundaryImpl
13
- resetKey={props.resetKey}
13
+ getResetKey={props.getResetKey}
14
14
  onCatch={props.onCatch}
15
15
  children={({ error }) => {
16
16
  if (error) {
@@ -26,23 +26,26 @@ export function CatchBoundary(props: {
26
26
  }
27
27
 
28
28
  export class CatchBoundaryImpl extends React.Component<{
29
- resetKey: string
29
+ getResetKey: () => string
30
30
  children: (props: { error: any; reset: () => void }) => any
31
31
  onCatch?: (error: any) => void
32
32
  }> {
33
33
  state = { error: null } as any
34
+ static getDerivedStateFromProps(props: any) {
35
+ return { resetKey: props.getResetKey() }
36
+ }
34
37
  static getDerivedStateFromError(error: any) {
35
38
  return { error }
36
39
  }
37
40
  componentDidUpdate(
38
41
  prevProps: Readonly<{
39
- resetKey: string
42
+ getResetKey: () => string
40
43
  children: (props: { error: any; reset: () => void }) => any
41
44
  onCatch?: ((error: any, info: any) => void) | undefined
42
45
  }>,
43
46
  prevState: any,
44
47
  ): void {
45
- if (prevState.error && prevProps.resetKey !== this.props.resetKey) {
48
+ if (prevState.error && prevState.resetKey !== this.state.resetKey) {
46
49
  this.setState({ error: null })
47
50
  }
48
51
  }
package/src/Matches.tsx CHANGED
@@ -13,9 +13,11 @@ import {
13
13
  RouteIds,
14
14
  RoutePaths,
15
15
  } from './routeInfo'
16
- import { RegisteredRouter } from './router'
16
+ import { RegisteredRouter, RouterState } from './router'
17
17
  import { NoInfer, StrictOrFrom, pick } from './utils'
18
18
 
19
+ export const matchContext = React.createContext<string | undefined>(undefined)
20
+
19
21
  export interface RouteMatch<
20
22
  TRouteTree extends AnyRoute = AnyRoute,
21
23
  TRouteId extends RouteIds<TRouteTree> = ParseRoute<TRouteTree>['id'],
@@ -48,11 +50,11 @@ export type AnyRouteMatch = RouteMatch<any>
48
50
 
49
51
  export function Matches() {
50
52
  const router = useRouter()
51
- const routerState = useRouterState()
52
- const matches = routerState.pendingMatches?.some((d) => d.showPending)
53
- ? routerState.pendingMatches
54
- : routerState.matches
55
- const locationKey = routerState.resolvedLocation.state.key
53
+ const matchId = useRouterState({
54
+ select: (s) => {
55
+ return getRenderedMatches(s)[0]?.id
56
+ },
57
+ })
56
58
  const route = router.routesById[rootRouteId]!
57
59
 
58
60
  const errorComponent = React.useCallback(
@@ -69,9 +71,9 @@ export function Matches() {
69
71
  )
70
72
 
71
73
  return (
72
- <matchesContext.Provider value={matches}>
74
+ <matchContext.Provider value={matchId}>
73
75
  <CatchBoundary
74
- resetKey={locationKey}
76
+ getResetKey={() => router.state.resolvedLocation.state?.key}
75
77
  errorComponent={errorComponent}
76
78
  onCatch={() => {
77
79
  warning(
@@ -80,9 +82,9 @@ export function Matches() {
80
82
  )
81
83
  }}
82
84
  >
83
- {matches.length ? <Match matches={matches} /> : null}
85
+ {matchId ? <Match matchId={matchId} /> : null}
84
86
  </CatchBoundary>
85
- </matchesContext.Provider>
87
+ </matchContext.Provider>
86
88
  )
87
89
  }
88
90
 
@@ -90,16 +92,22 @@ function SafeFragment(props: any) {
90
92
  return <>{props.children}</>
91
93
  }
92
94
 
93
- export function Match({ matches }: { matches: RouteMatch[] }) {
94
- const { options, routesById } = useRouter()
95
- const match = matches[0]!
96
- const routeId = match?.routeId
97
- const route = routesById[routeId]!
95
+ export function Match({ matchId }: { matchId: string }) {
98
96
  const router = useRouter()
99
- const locationKey = useRouterState().resolvedLocation.state?.key
97
+ const routeId = useRouterState({
98
+ select: (s) =>
99
+ getRenderedMatches(s).find((d) => d.id === matchId)?.routeId as string,
100
+ })
101
+
102
+ invariant(
103
+ routeId,
104
+ `Could not find routeId for matchId "${matchId}". Please file an issue!`,
105
+ )
106
+
107
+ const route = router.routesById[routeId]!
100
108
 
101
109
  const PendingComponent = (route.options.pendingComponent ??
102
- options.defaultPendingComponent) as any
110
+ router.options.defaultPendingComponent) as any
103
111
 
104
112
  const pendingElement = PendingComponent
105
113
  ? React.createElement(PendingComponent, {
@@ -112,7 +120,7 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
112
120
 
113
121
  const routeErrorComponent =
114
122
  route.options.errorComponent ??
115
- options.defaultErrorComponent ??
123
+ router.options.defaultErrorComponent ??
116
124
  ErrorComponent
117
125
 
118
126
  const ResolvedSuspenseBoundary =
@@ -138,30 +146,45 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
138
146
  const ResolvedCatchBoundary = errorComponent ? CatchBoundary : SafeFragment
139
147
 
140
148
  return (
141
- <matchesContext.Provider value={matches}>
149
+ <matchContext.Provider value={matchId}>
142
150
  <ResolvedSuspenseBoundary fallback={pendingElement}>
143
151
  <ResolvedCatchBoundary
144
- resetKey={locationKey}
152
+ getResetKey={() => router.state.resolvedLocation.state?.key}
145
153
  errorComponent={errorComponent}
146
154
  onCatch={() => {
147
- warning(false, `Error in route match: ${match.id}`)
155
+ warning(false, `Error in route match: ${matchId}`)
148
156
  }}
149
157
  >
150
- <MatchInner match={match} pendingElement={pendingElement} />
158
+ <MatchInner matchId={matchId!} pendingElement={pendingElement} />
151
159
  </ResolvedCatchBoundary>
152
160
  </ResolvedSuspenseBoundary>
153
- </matchesContext.Provider>
161
+ </matchContext.Provider>
154
162
  )
155
163
  }
156
164
  function MatchInner({
157
- match,
165
+ matchId,
158
166
  pendingElement,
159
167
  }: {
160
- match: RouteMatch
168
+ matchId: string
161
169
  pendingElement: any
162
170
  }): any {
163
- const { options, routesById } = useRouter()
164
- const route = routesById[match.routeId]!
171
+ const router = useRouter()
172
+ const routeId = useRouterState({
173
+ select: (s) =>
174
+ getRenderedMatches(s).find((d) => d.id === matchId)?.routeId as string,
175
+ })
176
+
177
+ const route = router.routesById[routeId]!
178
+
179
+ const match = useRouterState({
180
+ select: (s) =>
181
+ pick(getRenderedMatches(s).find((d) => d.id === matchId)!, [
182
+ 'status',
183
+ 'error',
184
+ 'showPending',
185
+ 'loadPromise',
186
+ ]),
187
+ })
165
188
 
166
189
  if (match.status === 'error') {
167
190
  throw match.error
@@ -175,7 +198,7 @@ function MatchInner({
175
198
  }
176
199
 
177
200
  if (match.status === 'success') {
178
- let comp = route.options.component ?? options.defaultComponent
201
+ let comp = route.options.component ?? router.options.defaultComponent
179
202
 
180
203
  if (comp) {
181
204
  return React.createElement(comp, {
@@ -197,13 +220,21 @@ function MatchInner({
197
220
  }
198
221
 
199
222
  export function Outlet() {
200
- const matches = React.useContext(matchesContext).slice(1)
223
+ const matchId = React.useContext(matchContext)
224
+
225
+ const childMatchId = useRouterState({
226
+ select: (s) => {
227
+ const matches = getRenderedMatches(s)
228
+ const index = matches.findIndex((d) => d.id === matchId)
229
+ return matches[index + 1]?.id
230
+ },
231
+ })
201
232
 
202
- if (!matches[0]) {
233
+ if (!childMatchId) {
203
234
  return null
204
235
  }
205
236
 
206
- return <Match matches={matches} />
237
+ return <Match matchId={childMatchId} />
207
238
  }
208
239
 
209
240
  export interface MatchRouteOptions {
@@ -291,6 +322,12 @@ export function MatchRoute<
291
322
  return !!params ? props.children : null
292
323
  }
293
324
 
325
+ function getRenderedMatches(state: RouterState) {
326
+ return state.pendingMatches?.some((d) => d.showPending)
327
+ ? state.pendingMatches
328
+ : state.matches
329
+ }
330
+
294
331
  export function useMatch<
295
332
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
296
333
  TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
@@ -302,22 +339,20 @@ export function useMatch<
302
339
  select?: (match: TRouteMatchState) => TSelected
303
340
  },
304
341
  ): TStrict extends true ? TSelected : TSelected | undefined {
305
- const nearestMatch = React.useContext(matchesContext)[0]!
306
- const nearestMatchRouteId = nearestMatch?.routeId
307
-
308
- const matchRouteId = useRouterState({
309
- select: (state) => {
310
- const matches = state.pendingMatches?.some((d) => d.showPending)
311
- ? state.pendingMatches
312
- : state.matches
342
+ const router = useRouter()
343
+ const nearestMatchId = React.useContext(matchContext)
313
344
 
314
- const match = opts?.from
315
- ? matches.find((d) => d.routeId === opts?.from)
316
- : matches.find((d) => d.id === nearestMatch.id)
345
+ const nearestMatchRouteId = getRenderedMatches(router.state).find(
346
+ (d) => d.id === nearestMatchId,
347
+ )?.routeId
317
348
 
318
- return match!.routeId
319
- },
320
- })
349
+ const matchRouteId = (() => {
350
+ const matches = getRenderedMatches(router.state)
351
+ const match = opts?.from
352
+ ? matches.find((d) => d.routeId === opts?.from)
353
+ : matches.find((d) => d.id === nearestMatchId)
354
+ return match!.routeId
355
+ })()
321
356
 
322
357
  if (opts?.strict ?? true) {
323
358
  invariant(
@@ -334,13 +369,9 @@ export function useMatch<
334
369
 
335
370
  const matchSelection = useRouterState({
336
371
  select: (state) => {
337
- const matches = state.pendingMatches?.some((d) => d.showPending)
338
- ? state.pendingMatches
339
- : state.matches
340
-
341
- const match = opts?.from
342
- ? matches.find((d) => d.routeId === opts?.from)
343
- : matches.find((d) => d.id === nearestMatch.id)
372
+ const match = getRenderedMatches(state).find(
373
+ (d) => d.id === nearestMatchId,
374
+ )
344
375
 
345
376
  invariant(
346
377
  match,
@@ -358,22 +389,15 @@ export function useMatch<
358
389
  return matchSelection as any
359
390
  }
360
391
 
361
- export const matchesContext = React.createContext<RouteMatch[]>(null!)
362
-
363
392
  export function useMatches<T = RouteMatch[]>(opts?: {
364
393
  select?: (matches: RouteMatch[]) => T
365
394
  }): T {
366
- const contextMatches = React.useContext(matchesContext)
395
+ const contextMatchId = React.useContext(matchContext)
367
396
 
368
397
  return useRouterState({
369
398
  select: (state) => {
370
- let matches = state.pendingMatches?.some((d) => d.showPending)
371
- ? state.pendingMatches
372
- : state.matches
373
-
374
- matches = matches.slice(
375
- matches.findIndex((d) => d.id === contextMatches[0]?.id),
376
- )
399
+ let matches = getRenderedMatches(state)
400
+ matches = matches.slice(matches.findIndex((d) => d.id === contextMatchId))
377
401
  return opts?.select ? opts.select(matches) : (matches as T)
378
402
  },
379
403
  })
@@ -393,9 +417,12 @@ export function useLoaderData<
393
417
  select?: (match: TRouteMatch) => TSelected
394
418
  },
395
419
  ): TStrict extends true ? TSelected : TSelected | undefined {
396
- const match = useMatch({ ...opts, select: undefined })!
397
-
398
- return typeof opts.select === 'function'
399
- ? opts.select(match?.loaderData)
400
- : match?.loaderData
420
+ return useMatch({
421
+ ...opts,
422
+ select: (s) => {
423
+ return typeof opts.select === 'function'
424
+ ? opts.select(s?.loaderData)
425
+ : s?.loaderData
426
+ },
427
+ })!
401
428
  }
package/src/link.tsx CHANGED
@@ -43,31 +43,31 @@ export type LinkInfo =
43
43
  export type CleanPath<T extends string> = T extends `${infer L}//${infer R}`
44
44
  ? CleanPath<`${CleanPath<L>}/${CleanPath<R>}`>
45
45
  : T extends `${infer L}//`
46
- ? `${CleanPath<L>}/`
47
- : T extends `//${infer L}`
48
- ? `/${CleanPath<L>}`
49
- : T
46
+ ? `${CleanPath<L>}/`
47
+ : T extends `//${infer L}`
48
+ ? `/${CleanPath<L>}`
49
+ : T
50
50
 
51
51
  export type Split<S, TIncludeTrailingSlash = true> = S extends unknown
52
52
  ? string extends S
53
53
  ? string[]
54
54
  : S extends string
55
- ? CleanPath<S> extends ''
56
- ? []
57
- : TIncludeTrailingSlash extends true
58
- ? CleanPath<S> extends `${infer T}/`
59
- ? [...Split<T>, '/']
60
- : CleanPath<S> extends `/${infer U}`
61
- ? Split<U>
62
- : CleanPath<S> extends `${infer T}/${infer U}`
63
- ? [...Split<T>, ...Split<U>]
64
- : [S]
65
- : CleanPath<S> extends `${infer T}/${infer U}`
66
- ? [...Split<T>, ...Split<U>]
67
- : S extends string
68
- ? [S]
55
+ ? CleanPath<S> extends ''
56
+ ? []
57
+ : TIncludeTrailingSlash extends true
58
+ ? CleanPath<S> extends `${infer T}/`
59
+ ? [...Split<T>, '/']
60
+ : CleanPath<S> extends `/${infer U}`
61
+ ? Split<U>
62
+ : CleanPath<S> extends `${infer T}/${infer U}`
63
+ ? [...Split<T>, ...Split<U>]
64
+ : [S]
65
+ : CleanPath<S> extends `${infer T}/${infer U}`
66
+ ? [...Split<T>, ...Split<U>]
67
+ : S extends string
68
+ ? [S]
69
+ : never
69
70
  : never
70
- : never
71
71
  : never
72
72
 
73
73
  export type ParsePathParams<T extends string> = keyof {
@@ -77,10 +77,10 @@ export type ParsePathParams<T extends string> = keyof {
77
77
  export type Join<T, Delimiter extends string = '/'> = T extends []
78
78
  ? ''
79
79
  : T extends [infer L extends string]
80
- ? L
81
- : T extends [infer L extends string, ...infer Tail extends [...string[]]]
82
- ? CleanPath<`${L}${Delimiter}${Join<Tail>}`>
83
- : never
80
+ ? L
81
+ : T extends [infer L extends string, ...infer Tail extends [...string[]]]
82
+ ? CleanPath<`${L}${Delimiter}${Join<Tail>}`>
83
+ : never
84
84
 
85
85
  export type Last<T extends any[]> = T extends [...infer _, infer L] ? L : never
86
86
 
@@ -112,23 +112,23 @@ export type RelativeToPathAutoComplete<
112
112
  >}`
113
113
  : never
114
114
  : TTo extends `./${infer RestTTo}`
115
- ? SplitPaths extends [
116
- ...Split<TFrom, false>,
117
- ...Split<RestTTo, false>,
118
- ...infer RestPath,
119
- ]
120
- ? `${TTo}${Join<RestPath>}`
121
- : never
122
- :
123
- | (TFrom extends `/`
124
- ? never
125
- : SplitPaths extends [...Split<TFrom, false>, ...infer RestPath]
126
- ? Join<RestPath> extends { length: 0 }
115
+ ? SplitPaths extends [
116
+ ...Split<TFrom, false>,
117
+ ...Split<RestTTo, false>,
118
+ ...infer RestPath,
119
+ ]
120
+ ? `${TTo}${Join<RestPath>}`
121
+ : never
122
+ :
123
+ | (TFrom extends `/`
127
124
  ? never
128
- : './'
129
- : never)
130
- | (TFrom extends `/` ? never : '../')
131
- | AllPaths
125
+ : SplitPaths extends [...Split<TFrom, false>, ...infer RestPath]
126
+ ? Join<RestPath> extends { length: 0 }
127
+ ? never
128
+ : './'
129
+ : never)
130
+ | (TFrom extends `/` ? never : '../')
131
+ | AllPaths
132
132
 
133
133
  export type NavigateOptions<
134
134
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
@@ -333,22 +333,22 @@ export type ResolveRelativePath<TFrom, TTo = '.'> = TFrom extends string
333
333
  ? TTo extends '.'
334
334
  ? TFrom
335
335
  : TTo extends `./`
336
- ? Join<[TFrom, '/']>
337
- : TTo extends `./${infer TRest}`
338
- ? ResolveRelativePath<TFrom, TRest>
339
- : TTo extends `/${infer TRest}`
340
- ? TTo
341
- : Split<TTo> extends ['..', ...infer ToRest]
342
- ? Split<TFrom> extends [...infer FromRest, infer FromTail]
343
- ? ToRest extends ['/']
344
- ? Join<[...FromRest, '/']>
345
- : ResolveRelativePath<Join<FromRest>, Join<ToRest>>
346
- : never
347
- : Split<TTo> extends ['.', ...infer ToRest]
348
- ? ToRest extends ['/']
349
336
  ? Join<[TFrom, '/']>
350
- : ResolveRelativePath<TFrom, Join<ToRest>>
351
- : CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
337
+ : TTo extends `./${infer TRest}`
338
+ ? ResolveRelativePath<TFrom, TRest>
339
+ : TTo extends `/${infer TRest}`
340
+ ? TTo
341
+ : Split<TTo> extends ['..', ...infer ToRest]
342
+ ? Split<TFrom> extends [...infer FromRest, infer FromTail]
343
+ ? ToRest extends ['/']
344
+ ? Join<[...FromRest, '/']>
345
+ : ResolveRelativePath<Join<FromRest>, Join<ToRest>>
346
+ : never
347
+ : Split<TTo> extends ['.', ...infer ToRest]
348
+ ? ToRest extends ['/']
349
+ ? Join<[TFrom, '/']>
350
+ : ResolveRelativePath<TFrom, Join<ToRest>>
351
+ : CleanPath<Join<['/', ...Split<TFrom>, ...Split<TTo>]>>
352
352
  : never
353
353
  : never
354
354
 
@@ -443,8 +443,8 @@ export function useLinkProps<
443
443
  href: disabled
444
444
  ? undefined
445
445
  : next.maskedLocation
446
- ? next.maskedLocation.href
447
- : next.href,
446
+ ? next.maskedLocation.href
447
+ : next.href,
448
448
  onClick: composeHandlers([onClick, handleClick]),
449
449
  onFocus: composeHandlers([onFocus, handleFocus]),
450
450
  onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
package/src/router.ts CHANGED
@@ -601,7 +601,7 @@ export class Router<
601
601
  }
602
602
  })()
603
603
 
604
- const interpolatedPath = interpolatePath(route.path, routeParams)
604
+ const interpolatedPath = interpolatePath(route.fullPath, routeParams)
605
605
  const matchId =
606
606
  interpolatePath(route.id, routeParams, true) +
607
607
  (route.options.key?.({