@tanstack/router-core 0.0.1-beta.1 → 0.0.1-beta.11

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/router-core",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.1",
4
+ "version": "0.0.1-beta.11",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -33,6 +33,7 @@
33
33
  "build/**",
34
34
  "src"
35
35
  ],
36
+ "sideEffects": false,
36
37
  "peerDependencies": {
37
38
  "react": ">=16",
38
39
  "react-dom": ">=16"
package/src/qss.ts CHANGED
@@ -30,6 +30,7 @@ function toValue(mix) {
30
30
  var str = decodeURIComponent(mix)
31
31
  if (str === 'false') return false
32
32
  if (str === 'true') return true
33
+ if (str.charAt(0) === '0') return str
33
34
  return +str * 0 === 0 ? +str : str
34
35
  }
35
36
 
package/src/route.ts CHANGED
@@ -13,7 +13,6 @@ import {
13
13
  RouteInfo,
14
14
  RouteInfoByPath,
15
15
  } from './routeInfo'
16
- import { RouteMatch } from './routeMatch'
17
16
  import {
18
17
  Action,
19
18
  ActionState,
@@ -22,7 +21,7 @@ import {
22
21
  MatchRouteOptions,
23
22
  Router,
24
23
  } from './router'
25
- import { NoInfer, replaceEqualDeep } from './utils'
24
+ import { NoInfer } from './utils'
26
25
 
27
26
  export interface AnyRoute extends Route<any, any> {}
28
27
 
@@ -96,10 +95,10 @@ export function createRoute<
96
95
  router.state.actions[id] ||
97
96
  (() => {
98
97
  router.state.actions[id] = {
99
- pending: [],
98
+ submissions: [],
100
99
  submit: async <T, U>(
101
100
  submission: T,
102
- actionOpts?: { invalidate?: boolean },
101
+ actionOpts?: { invalidate?: boolean; multi?: boolean },
103
102
  ) => {
104
103
  if (!route) {
105
104
  return
@@ -107,27 +106,27 @@ export function createRoute<
107
106
 
108
107
  const invalidate = actionOpts?.invalidate ?? true
109
108
 
109
+ if (!actionOpts?.multi) {
110
+ action.submissions = action.submissions.filter((d) => d.isMulti)
111
+ }
112
+
110
113
  const actionState: ActionState<T, U> = {
111
114
  submittedAt: Date.now(),
112
115
  status: 'pending',
113
116
  submission,
117
+ isMulti: !!actionOpts?.multi,
114
118
  }
115
119
 
116
120
  action.current = actionState
117
121
  action.latest = actionState
118
- action.pending.push(actionState)
119
-
120
- router.state = {
121
- ...router.state,
122
- currentAction: actionState,
123
- latestAction: actionState,
124
- }
122
+ action.submissions.push(actionState)
125
123
 
126
124
  router.notify()
127
125
 
128
126
  try {
129
127
  const res = await route.options.action?.(submission)
130
128
  actionState.data = res as U
129
+
131
130
  if (invalidate) {
132
131
  router.invalidateRoute({ to: '.', fromCurrent: true })
133
132
  await router.reload()
@@ -139,8 +138,6 @@ export function createRoute<
139
138
  actionState.error = err
140
139
  actionState.status = 'error'
141
140
  } finally {
142
- action.pending = action.pending.filter((d) => d !== actionState)
143
- router.removeActionQueue.push({ action, actionState })
144
141
  router.notify()
145
142
  }
146
143
  },
@@ -228,16 +225,3 @@ export function createRoute<
228
225
 
229
226
  return route
230
227
  }
231
-
232
- export function cascadeLoaderData(matches: RouteMatch<any, any>[]) {
233
- matches.forEach((match, index) => {
234
- const parent = matches[index - 1]
235
-
236
- if (parent) {
237
- match.loaderData = replaceEqualDeep(match.loaderData, {
238
- ...parent.loaderData,
239
- ...match.routeLoaderData,
240
- })
241
- }
242
- })
243
- }
package/src/routeMatch.ts CHANGED
@@ -1,13 +1,12 @@
1
1
  import { GetFrameworkGeneric } from './frameworks'
2
2
  import { Route } from './route'
3
- import { AnyPathParams } from './routeConfig'
4
3
  import {
5
4
  AnyAllRouteInfo,
6
5
  AnyRouteInfo,
7
6
  DefaultAllRouteInfo,
8
7
  RouteInfo,
9
8
  } from './routeInfo'
10
- import { Router } from './router'
9
+ import { ActionState, Router } from './router'
11
10
  import { replaceEqualDeep, Timeout } from './utils'
12
11
 
13
12
  export interface RouteMatch<
@@ -37,7 +36,7 @@ export interface RouteMatch<
37
36
  catchElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
38
37
  pendingElement?: GetFrameworkGeneric<'Element'> // , TRouteInfo['loaderData']>
39
38
  loadPromise?: Promise<void>
40
- loaderPromise?: Promise<void>
39
+ loaderDataPromise?: Promise<void>
41
40
  elementsPromise?: Promise<void>
42
41
  dataPromise?: Promise<void>
43
42
  pendingTimeout?: Timeout
@@ -104,6 +103,7 @@ export function createRouteMatch<
104
103
  isFetching: false,
105
104
  isInvalid: false,
106
105
  invalidAt: Infinity,
106
+ // pendingActions: [],
107
107
  getIsInvalid: () => {
108
108
  const now = Date.now()
109
109
  return routeMatch.isInvalid || routeMatch.invalidAt < now
@@ -147,18 +147,6 @@ export function createRouteMatch<
147
147
  clearTimeout(routeMatch.__.pendingMinTimeout)
148
148
  delete routeMatch.__.pendingMinPromise
149
149
  },
150
- // setParentMatch: (parentMatch?: RouteMatch) => {
151
- // routeMatch.parentMatch = parentMatch
152
- // },
153
- // addChildMatch: (childMatch: RouteMatch) => {
154
- // if (
155
- // routeMatch.childMatches.find((d) => d.matchId === childMatch.matchId)
156
- // ) {
157
- // return
158
- // }
159
-
160
- // routeMatch.childMatches.push(childMatch)
161
- // },
162
150
  validate: () => {
163
151
  // Validate the search params and stabilize them
164
152
  const parentSearch =
@@ -174,7 +162,7 @@ export function createRouteMatch<
174
162
 
175
163
  let nextSearch = replaceEqualDeep(
176
164
  prevSearch,
177
- validator?.(parentSearch),
165
+ validator?.(parentSearch) ?? {},
178
166
  )
179
167
 
180
168
  // Invalidate route matches when search param stability changes
@@ -188,6 +176,14 @@ export function createRouteMatch<
188
176
  ...parentSearch,
189
177
  ...nextSearch,
190
178
  })
179
+
180
+ elementTypes.map(async (type) => {
181
+ const routeElement = routeMatch.options[type]
182
+
183
+ if (typeof routeMatch.__[type] !== 'function') {
184
+ routeMatch.__[type] = routeElement
185
+ }
186
+ })
191
187
  } catch (err: any) {
192
188
  console.error(err)
193
189
  const error = new (Error as any)('Invalid search params found', {
@@ -243,7 +239,7 @@ export function createRouteMatch<
243
239
  ) {
244
240
  const maxAge = loaderOpts?.preload ? loaderOpts?.maxAge : undefined
245
241
 
246
- routeMatch.fetch({ maxAge })
242
+ await routeMatch.fetch({ maxAge })
247
243
  }
248
244
  },
249
245
  fetch: async (opts) => {
@@ -266,7 +262,7 @@ export function createRouteMatch<
266
262
  routeMatch.isFetching = true
267
263
  routeMatch.__.resolve = resolve as () => void
268
264
 
269
- const loaderPromise = (async () => {
265
+ routeMatch.__.loaderDataPromise = (async () => {
270
266
  // Load the elements and data in parallel
271
267
 
272
268
  routeMatch.__.elementsPromise = (async () => {
@@ -277,13 +273,11 @@ export function createRouteMatch<
277
273
  elementTypes.map(async (type) => {
278
274
  const routeElement = routeMatch.options[type]
279
275
 
280
- if (routeMatch.__[type]) {
281
- return
276
+ if (typeof routeMatch.__[type] === 'function') {
277
+ routeMatch.__[type] = await router.options.createElement!(
278
+ routeElement,
279
+ )
282
280
  }
283
-
284
- routeMatch.__[type] = await router.options.createElement!(
285
- routeElement,
286
- )
287
281
  }),
288
282
  )
289
283
  })()
@@ -297,7 +291,7 @@ export function createRouteMatch<
297
291
  signal: routeMatch.__.abortController.signal,
298
292
  })
299
293
  if (id !== routeMatch.__.latestId) {
300
- return routeMatch.__.loaderPromise
294
+ return routeMatch.__.loadPromise
301
295
  }
302
296
 
303
297
  routeMatch.routeLoaderData = replaceEqualDeep(
@@ -317,7 +311,7 @@ export function createRouteMatch<
317
311
  0)
318
312
  } catch (err) {
319
313
  if (id !== routeMatch.__.latestId) {
320
- return routeMatch.__.loaderPromise
314
+ return routeMatch.__.loadPromise
321
315
  }
322
316
 
323
317
  if (process.env.NODE_ENV !== 'production') {
@@ -335,7 +329,7 @@ export function createRouteMatch<
335
329
  routeMatch.__.dataPromise,
336
330
  ])
337
331
  if (id !== routeMatch.__.latestId) {
338
- return routeMatch.__.loaderPromise
332
+ return routeMatch.__.loadPromise
339
333
  }
340
334
 
341
335
  if (routeMatch.__.pendingMinPromise) {
@@ -344,7 +338,7 @@ export function createRouteMatch<
344
338
  }
345
339
  } finally {
346
340
  if (id !== routeMatch.__.latestId) {
347
- return routeMatch.__.loaderPromise
341
+ return routeMatch.__.loadPromise
348
342
  }
349
343
  routeMatch.__.cancelPending()
350
344
  routeMatch.isPending = false
@@ -353,16 +347,18 @@ export function createRouteMatch<
353
347
  }
354
348
  })()
355
349
 
356
- routeMatch.__.loaderPromise = loaderPromise
357
- await loaderPromise
350
+ await routeMatch.__.loaderDataPromise
358
351
 
359
352
  if (id !== routeMatch.__.latestId) {
360
- return routeMatch.__.loaderPromise
353
+ return routeMatch.__.loadPromise
361
354
  }
362
- delete routeMatch.__.loaderPromise
355
+
356
+ delete routeMatch.__.loaderDataPromise
363
357
  })
364
358
 
365
- return await routeMatch.__.loadPromise
359
+ await routeMatch.__.loadPromise
360
+
361
+ delete routeMatch.__.loadPromise
366
362
  },
367
363
  }
368
364
 
package/src/router.ts CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  matchPathname,
24
24
  resolvePath,
25
25
  } from './path'
26
- import { AnyRoute, cascadeLoaderData, createRoute, Route } from './route'
26
+ import { AnyRoute, createRoute, Route } from './route'
27
27
  import {
28
28
  AnyLoaderData,
29
29
  AnyPathParams,
@@ -45,6 +45,7 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
45
45
  import {
46
46
  functionalUpdate,
47
47
  last,
48
+ pick,
48
49
  PickAsRequired,
49
50
  PickRequired,
50
51
  replaceEqualDeep,
@@ -104,9 +105,7 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
104
105
  createRouter?: (router: Router<any, any>) => void
105
106
  createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
106
107
  createElement?: (
107
- element:
108
- | GetFrameworkGeneric<'Element'>
109
- | (() => Promise<GetFrameworkGeneric<'Element'>>),
108
+ element: GetFrameworkGeneric<'SyncOrAsyncElement'>,
110
109
  ) => Promise<GetFrameworkGeneric<'Element'>>
111
110
  }
112
111
 
@@ -115,10 +114,13 @@ export interface Action<
115
114
  TResponse = unknown,
116
115
  // TError = unknown,
117
116
  > {
118
- submit: (submission?: TPayload) => Promise<TResponse>
117
+ submit: (
118
+ submission?: TPayload,
119
+ actionOpts?: { invalidate?: boolean; multi?: boolean },
120
+ ) => Promise<TResponse>
119
121
  current?: ActionState<TPayload, TResponse>
120
122
  latest?: ActionState<TPayload, TResponse>
121
- pending: ActionState<TPayload, TResponse>[]
123
+ submissions: ActionState<TPayload, TResponse>[]
122
124
  }
123
125
 
124
126
  export interface ActionState<
@@ -129,6 +131,7 @@ export interface ActionState<
129
131
  submittedAt: number
130
132
  status: 'idle' | 'pending' | 'success' | 'error'
131
133
  submission: TPayload
134
+ isMulti: boolean
132
135
  data?: TResponse
133
136
  error?: unknown
134
137
  }
@@ -174,9 +177,6 @@ export interface RouterState {
174
177
  location: Location
175
178
  matches: RouteMatch[]
176
179
  lastUpdated: number
177
- loaderData: unknown
178
- currentAction?: ActionState
179
- latestAction?: ActionState
180
180
  actions: Record<string, Action>
181
181
  loaders: Record<string, Loader>
182
182
  pending?: PendingState
@@ -189,7 +189,7 @@ export interface PendingState {
189
189
  matches: RouteMatch[]
190
190
  }
191
191
 
192
- type Listener = () => void
192
+ type Listener = (router: Router<any, any>) => void
193
193
 
194
194
  export type ListenerFn = () => void
195
195
 
@@ -227,6 +227,22 @@ type LinkCurrentTargetElement = {
227
227
  preloadTimeout?: null | ReturnType<typeof setTimeout>
228
228
  }
229
229
 
230
+ interface DehydratedRouterState
231
+ extends Pick<RouterState, 'status' | 'location' | 'lastUpdated'> {
232
+ matches: DehydratedRouteMatch[]
233
+ }
234
+
235
+ interface DehydratedRouteMatch
236
+ extends Pick<
237
+ RouteMatch<any, any>,
238
+ | 'matchId'
239
+ | 'status'
240
+ | 'routeLoaderData'
241
+ | 'loaderData'
242
+ | 'isInvalid'
243
+ | 'invalidAt'
244
+ > {}
245
+
230
246
  export interface Router<
231
247
  TRouteConfig extends AnyRouteConfig = RouteConfig,
232
248
  TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
@@ -248,10 +264,10 @@ export interface Router<
248
264
  routeTree: Route<TAllRouteInfo, RouteInfo>
249
265
  routesById: RoutesById<TAllRouteInfo>
250
266
  navigationPromise: Promise<void>
251
- removeActionQueue: { action: Action; actionState: ActionState }[]
252
267
  startedLoadingAt: number
253
268
  resolveNavigation: () => void
254
269
  subscribe: (listener: Listener) => () => void
270
+ reset: () => void
255
271
  notify: () => void
256
272
  mount: () => () => void
257
273
  onFocus: () => void
@@ -261,7 +277,7 @@ export interface Router<
261
277
 
262
278
  buildNext: (opts: BuildNextOptions) => Location
263
279
  cancelMatches: () => void
264
- loadLocation: (next?: Location) => Promise<void>
280
+ load: (next?: Location) => Promise<void>
265
281
  matchCache: Record<string, MatchCacheEntry>
266
282
  cleanMatchCache: () => void
267
283
  getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
@@ -305,6 +321,8 @@ export interface Router<
305
321
  >(
306
322
  opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
307
323
  ) => LinkInfo
324
+ dehydrateState: () => DehydratedRouterState
325
+ hydrateState: (state: DehydratedRouterState) => void
308
326
  __: {
309
327
  buildRouteTree: (
310
328
  routeConfig: RouteConfig,
@@ -322,13 +340,25 @@ export interface Router<
322
340
  }
323
341
 
324
342
  // Detect if we're in the DOM
325
- const isServer = Boolean(
326
- typeof window === 'undefined' || !window.document?.createElement,
327
- )
343
+ const isServer =
344
+ typeof window === 'undefined' || !window.document?.createElement
328
345
 
329
346
  // This is the default history object if none is defined
330
347
  const createDefaultHistory = () =>
331
- !isServer ? createBrowserHistory() : createMemoryHistory()
348
+ isServer ? createMemoryHistory() : createBrowserHistory()
349
+
350
+ function getInitialRouterState(): RouterState {
351
+ return {
352
+ status: 'idle',
353
+ location: null!,
354
+ matches: [],
355
+ actions: {},
356
+ loaders: {},
357
+ lastUpdated: Date.now(),
358
+ isFetching: false,
359
+ isPreloading: false,
360
+ }
361
+ }
332
362
 
333
363
  export function createRouter<
334
364
  TRouteConfig extends AnyRouteConfig = RouteConfig,
@@ -352,7 +382,6 @@ export function createRouter<
352
382
  history,
353
383
  options: originalOptions,
354
384
  listeners: [],
355
- removeActionQueue: [],
356
385
  // Resolved after construction
357
386
  basepath: '',
358
387
  routeTree: undefined!,
@@ -363,16 +392,10 @@ export function createRouter<
363
392
  navigationPromise: Promise.resolve(),
364
393
  resolveNavigation: () => {},
365
394
  matchCache: {},
366
- state: {
367
- status: 'idle',
368
- location: null!,
369
- matches: [],
370
- actions: {},
371
- loaders: {},
372
- loaderData: {} as any,
373
- lastUpdated: Date.now(),
374
- isFetching: false,
375
- isPreloading: false,
395
+ state: getInitialRouterState(),
396
+ reset: () => {
397
+ router.state = getInitialRouterState()
398
+ router.notify()
376
399
  },
377
400
  startedLoadingAt: Date.now(),
378
401
  subscribe: (listener: Listener): (() => void) => {
@@ -398,7 +421,47 @@ export function createRouter<
398
421
  }
399
422
 
400
423
  cascadeLoaderData(router.state.matches)
401
- router.listeners.forEach((listener) => listener())
424
+ router.listeners.forEach((listener) => listener(router))
425
+ },
426
+
427
+ dehydrateState: () => {
428
+ return {
429
+ ...pick(router.state, ['status', 'location', 'lastUpdated']),
430
+ matches: router.state.matches.map((match) =>
431
+ pick(match, [
432
+ 'matchId',
433
+ 'status',
434
+ 'routeLoaderData',
435
+ 'loaderData',
436
+ 'isInvalid',
437
+ 'invalidAt',
438
+ ]),
439
+ ),
440
+ }
441
+ },
442
+
443
+ hydrateState: (dehydratedState) => {
444
+ // Match the routes
445
+ const matches = router.matchRoutes(router.location.pathname, {
446
+ strictParseParams: true,
447
+ })
448
+
449
+ matches.forEach((match, index) => {
450
+ const dehydratedMatch = dehydratedState.matches[index]
451
+ invariant(
452
+ dehydratedMatch,
453
+ 'Oh no! Dehydrated route matches did not match the active state of the router 😬',
454
+ )
455
+ Object.assign(match, dehydratedMatch)
456
+ })
457
+
458
+ router.loadMatches(matches)
459
+
460
+ router.state = {
461
+ ...router.state,
462
+ ...dehydratedState,
463
+ matches,
464
+ }
402
465
  },
403
466
 
404
467
  mount: () => {
@@ -412,14 +475,12 @@ export function createRouter<
412
475
  // to the current location. Otherwise, load the current location.
413
476
  if (next.href !== router.location.href) {
414
477
  router.__.commitLocation(next, true)
415
- } else {
416
- router.loadLocation()
417
478
  }
418
479
 
419
- const unsub = history.listen((event) => {
420
- router.loadLocation(
421
- router.__.parseLocation(event.location, router.location),
422
- )
480
+ // router.load()
481
+
482
+ const unsub = router.history.listen((event) => {
483
+ router.load(router.__.parseLocation(event.location, router.location))
423
484
  })
424
485
 
425
486
  // addEventListener does not exist in React Native, but window does
@@ -432,17 +493,28 @@ export function createRouter<
432
493
 
433
494
  return () => {
434
495
  unsub()
435
- // Be sure to unsubscribe if a new handler is set
436
- window.removeEventListener('visibilitychange', router.onFocus)
437
- window.removeEventListener('focus', router.onFocus)
496
+ if (!isServer && window.removeEventListener) {
497
+ // Be sure to unsubscribe if a new handler is set
498
+ window.removeEventListener('visibilitychange', router.onFocus)
499
+ window.removeEventListener('focus', router.onFocus)
500
+ }
438
501
  }
439
502
  },
440
503
 
441
504
  onFocus: () => {
442
- router.loadLocation()
505
+ router.load()
443
506
  },
444
507
 
445
508
  update: (opts) => {
509
+ const newHistory = opts?.history !== router.history
510
+ if (!router.location || newHistory) {
511
+ if (opts?.history) {
512
+ router.history = opts.history
513
+ }
514
+ router.location = router.__.parseLocation(router.history.location)
515
+ router.state.location = router.location
516
+ }
517
+
446
518
  Object.assign(router.options, opts)
447
519
 
448
520
  const { basepath, routeConfig } = router.options
@@ -466,7 +538,7 @@ export function createRouter<
466
538
  })
467
539
  },
468
540
 
469
- loadLocation: async (next?: Location) => {
541
+ load: async (next?: Location) => {
470
542
  const id = Math.random()
471
543
  router.startedLoadingAt = id
472
544
 
@@ -475,22 +547,11 @@ export function createRouter<
475
547
  router.location = next
476
548
  }
477
549
 
478
- // Clear out old actions
479
- router.removeActionQueue.forEach(({ action, actionState }) => {
480
- if (router.state.currentAction === actionState) {
481
- router.state.currentAction = undefined
482
- }
483
- if (action.current === actionState) {
484
- action.current = undefined
485
- }
486
- })
487
- router.removeActionQueue = []
488
-
489
550
  // Cancel any pending matches
490
551
  router.cancelMatches()
491
552
 
492
553
  // Match the routes
493
- const matches = router.matchRoutes(location.pathname, {
554
+ const matches = router.matchRoutes(router.location.pathname, {
494
555
  strictParseParams: true,
495
556
  })
496
557
 
@@ -528,6 +589,10 @@ export function createRouter<
528
589
  }
529
590
  })
530
591
 
592
+ const entering = matches.filter((d) => {
593
+ return !previousMatches.find((dd) => dd.matchId === d.matchId)
594
+ })
595
+
531
596
  const now = Date.now()
532
597
 
533
598
  exiting.forEach((d) => {
@@ -535,6 +600,7 @@ export function createRouter<
535
600
  params: d.params,
536
601
  search: d.routeSearch,
537
602
  })
603
+
538
604
  // Clear idle error states when match leaves
539
605
  if (d.status === 'error' && !d.isFetching) {
540
606
  d.status = 'idle'
@@ -559,10 +625,6 @@ export function createRouter<
559
625
  })
560
626
  })
561
627
 
562
- const entering = matches.filter((d) => {
563
- return !previousMatches.find((dd) => dd.matchId === d.matchId)
564
- })
565
-
566
628
  entering.forEach((d) => {
567
629
  d.__.onExit = d.options.onMatch?.({
568
630
  params: d.params,
@@ -571,17 +633,19 @@ export function createRouter<
571
633
  delete router.matchCache[d.matchId]
572
634
  })
573
635
 
574
- if (matches.some((d) => d.status === 'loading')) {
575
- router.notify()
576
- await Promise.all(
577
- matches.map((d) => d.__.loaderPromise || Promise.resolve()),
578
- )
579
- }
580
636
  if (router.startedLoadingAt !== id) {
581
637
  // Ignore side-effects of match loading
582
638
  return
583
639
  }
584
640
 
641
+ matches.forEach((match) => {
642
+ // Clear actions
643
+ if (match.action) {
644
+ match.action.current = undefined
645
+ match.action.submissions = []
646
+ }
647
+ })
648
+
585
649
  router.state = {
586
650
  ...router.state,
587
651
  location: router.location,
@@ -760,7 +824,9 @@ export function createRouter<
760
824
  if (match.status === 'loading') {
761
825
  // If requested, start the pending timers
762
826
  if (loaderOpts?.withPending) match.__.startPending()
827
+ }
763
828
 
829
+ if (match.__.loadPromise) {
764
830
  // Wait for the first sign of activity from the match
765
831
  // This might be completion, error, or a pending state
766
832
  await match.__.loadPromise
@@ -1014,12 +1080,6 @@ export function createRouter<
1014
1080
  return routeConfigs.map((routeConfig) => {
1015
1081
  const routeOptions = routeConfig.options
1016
1082
  const route = createRoute(routeConfig, routeOptions, parent, router)
1017
-
1018
- // {
1019
- // pendingMs: routeOptions.pendingMs ?? router.defaultPendingMs,
1020
- // pendingMinMs: routeOptions.pendingMinMs ?? router.defaultPendingMinMs,
1021
- // }
1022
-
1023
1083
  const existingRoute = (router.routesById as any)[route.routeId]
1024
1084
 
1025
1085
  if (existingRoute) {
@@ -1214,9 +1274,6 @@ export function createRouter<
1214
1274
  },
1215
1275
  }
1216
1276
 
1217
- router.location = router.__.parseLocation(history.location)
1218
- router.state.location = router.location
1219
-
1220
1277
  router.update(userOptions)
1221
1278
 
1222
1279
  // Allow frameworks to hook into the router creation
@@ -1228,3 +1285,16 @@ export function createRouter<
1228
1285
  function isCtrlEvent(e: MouseEvent) {
1229
1286
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
1230
1287
  }
1288
+
1289
+ function cascadeLoaderData(matches: RouteMatch<any, any>[]) {
1290
+ matches.forEach((match, index) => {
1291
+ const parent = matches[index - 1]
1292
+
1293
+ if (parent) {
1294
+ match.loaderData = replaceEqualDeep(match.loaderData, {
1295
+ ...parent.loaderData,
1296
+ ...match.routeLoaderData,
1297
+ })
1298
+ }
1299
+ })
1300
+ }