@tanstack/router-core 0.0.1-beta.2 → 0.0.1-beta.23

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.
Files changed (35) hide show
  1. package/build/cjs/packages/router-core/src/index.js +1 -1
  2. package/build/cjs/packages/router-core/src/path.js +1 -4
  3. package/build/cjs/packages/router-core/src/path.js.map +1 -1
  4. package/build/cjs/packages/router-core/src/qss.js +1 -0
  5. package/build/cjs/packages/router-core/src/qss.js.map +1 -1
  6. package/build/cjs/packages/router-core/src/route.js +9 -23
  7. package/build/cjs/packages/router-core/src/route.js.map +1 -1
  8. package/build/cjs/packages/router-core/src/routeConfig.js.map +1 -1
  9. package/build/cjs/packages/router-core/src/routeMatch.js +75 -121
  10. package/build/cjs/packages/router-core/src/routeMatch.js.map +1 -1
  11. package/build/cjs/packages/router-core/src/router.js +183 -89
  12. package/build/cjs/packages/router-core/src/router.js.map +1 -1
  13. package/build/cjs/packages/router-core/src/utils.js +7 -6
  14. package/build/cjs/packages/router-core/src/utils.js.map +1 -1
  15. package/build/esm/index.js +271 -237
  16. package/build/esm/index.js.map +1 -1
  17. package/build/stats-html.html +1 -1
  18. package/build/stats-react.json +139 -152
  19. package/build/types/index.d.ts +199 -191
  20. package/build/umd/index.development.js +271 -237
  21. package/build/umd/index.development.js.map +1 -1
  22. package/build/umd/index.production.js +1 -1
  23. package/build/umd/index.production.js.map +1 -1
  24. package/package.json +2 -1
  25. package/src/frameworks.ts +1 -2
  26. package/src/index.ts +0 -1
  27. package/src/link.ts +3 -1
  28. package/src/path.ts +0 -4
  29. package/src/qss.ts +1 -0
  30. package/src/route.ts +10 -26
  31. package/src/routeConfig.ts +30 -21
  32. package/src/routeInfo.ts +13 -3
  33. package/src/routeMatch.ts +94 -156
  34. package/src/router.ts +269 -109
  35. package/src/utils.ts +12 -5
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,
@@ -89,23 +90,23 @@ export interface RouterOptions<TRouteConfig extends AnyRouteConfig> {
89
90
  defaultPreloadMaxAge?: number
90
91
  defaultPreloadGcMaxAge?: number
91
92
  defaultPreloadDelay?: number
92
- useErrorBoundary?: boolean
93
- defaultElement?: GetFrameworkGeneric<'Element'>
94
- defaultErrorElement?: GetFrameworkGeneric<'Element'>
95
- defaultCatchElement?: GetFrameworkGeneric<'Element'>
96
- defaultPendingElement?: GetFrameworkGeneric<'Element'>
97
- defaultPendingMs?: number
98
- defaultPendingMinMs?: number
93
+ defaultComponent?: GetFrameworkGeneric<'Component'>
94
+ defaultErrorComponent?: GetFrameworkGeneric<'Component'>
95
+ defaultPendingComponent?: GetFrameworkGeneric<'Component'>
99
96
  defaultLoaderMaxAge?: number
100
97
  defaultLoaderGcMaxAge?: number
101
98
  caseSensitive?: boolean
102
99
  routeConfig?: TRouteConfig
103
100
  basepath?: string
101
+ useServerData?: boolean
104
102
  createRouter?: (router: Router<any, any>) => void
105
103
  createRoute?: (opts: { route: AnyRoute; router: Router<any, any> }) => void
106
- createElement?: (
107
- element: GetFrameworkGeneric<'SyncOrAsyncElement'>,
108
- ) => Promise<GetFrameworkGeneric<'Element'>>
104
+ loadComponent?: (
105
+ component: GetFrameworkGeneric<'Component'>,
106
+ ) => Promise<GetFrameworkGeneric<'Component'>>
107
+ // renderComponent?: (
108
+ // component: GetFrameworkGeneric<'Component'>,
109
+ // ) => GetFrameworkGeneric<'Element'>
109
110
  }
110
111
 
111
112
  export interface Action<
@@ -113,10 +114,13 @@ export interface Action<
113
114
  TResponse = unknown,
114
115
  // TError = unknown,
115
116
  > {
116
- submit: (submission?: TPayload) => Promise<TResponse>
117
+ submit: (
118
+ submission?: TPayload,
119
+ actionOpts?: { invalidate?: boolean; multi?: boolean },
120
+ ) => Promise<TResponse>
117
121
  current?: ActionState<TPayload, TResponse>
118
122
  latest?: ActionState<TPayload, TResponse>
119
- pending: ActionState<TPayload, TResponse>[]
123
+ submissions: ActionState<TPayload, TResponse>[]
120
124
  }
121
125
 
122
126
  export interface ActionState<
@@ -127,6 +131,7 @@ export interface ActionState<
127
131
  submittedAt: number
128
132
  status: 'idle' | 'pending' | 'success' | 'error'
129
133
  submission: TPayload
134
+ isMulti: boolean
130
135
  data?: TResponse
131
136
  error?: unknown
132
137
  }
@@ -160,21 +165,21 @@ export interface Loader<
160
165
  }
161
166
 
162
167
  export interface LoaderState<
163
- TFullSearchSchema = unknown,
164
- TAllParams = unknown,
168
+ TFullSearchSchema extends AnySearchSchema = {},
169
+ TAllParams extends AnyPathParams = {},
165
170
  > {
166
171
  loadedAt: number
167
172
  loaderContext: LoaderContext<TFullSearchSchema, TAllParams>
168
173
  }
169
174
 
170
- export interface RouterState {
175
+ export interface RouterState<
176
+ TSearchObj extends AnySearchSchema = {},
177
+ TState extends LocationState = LocationState,
178
+ > {
171
179
  status: 'idle' | 'loading'
172
- location: Location
180
+ location: Location<TSearchObj, TState>
173
181
  matches: RouteMatch[]
174
182
  lastUpdated: number
175
- loaderData: unknown
176
- currentAction?: ActionState
177
- latestAction?: ActionState
178
183
  actions: Record<string, Action>
179
184
  loaders: Record<string, Loader>
180
185
  pending?: PendingState
@@ -196,6 +201,7 @@ export interface BuildNextOptions {
196
201
  params?: true | Updater<Record<string, any>>
197
202
  search?: true | Updater<unknown>
198
203
  hash?: true | Updater<string>
204
+ state?: LocationState
199
205
  key?: string
200
206
  from?: string
201
207
  fromCurrent?: boolean
@@ -225,10 +231,35 @@ type LinkCurrentTargetElement = {
225
231
  preloadTimeout?: null | ReturnType<typeof setTimeout>
226
232
  }
227
233
 
234
+ interface DehydratedRouterState
235
+ extends Pick<RouterState, 'status' | 'location' | 'lastUpdated'> {
236
+ matches: DehydratedRouteMatch[]
237
+ }
238
+
239
+ interface DehydratedRouteMatch
240
+ extends Pick<
241
+ RouteMatch<any, any>,
242
+ | 'matchId'
243
+ | 'status'
244
+ | 'routeLoaderData'
245
+ | 'loaderData'
246
+ | 'isInvalid'
247
+ | 'invalidAt'
248
+ > {}
249
+
250
+ export interface RouterContext {}
251
+
228
252
  export interface Router<
229
253
  TRouteConfig extends AnyRouteConfig = RouteConfig,
230
254
  TAllRouteInfo extends AnyAllRouteInfo = AllRouteInfo<TRouteConfig>,
231
255
  > {
256
+ types: {
257
+ // Super secret internal stuff
258
+ RouteConfig: TRouteConfig
259
+ AllRouteInfo: TAllRouteInfo
260
+ }
261
+
262
+ // Public API
232
263
  history: BrowserHistory | MemoryHistory | HashHistory
233
264
  options: PickAsRequired<
234
265
  RouterOptions<TRouteConfig>,
@@ -237,19 +268,19 @@ export interface Router<
237
268
  // Computed in this.update()
238
269
  basepath: string
239
270
  // Internal:
240
- allRouteInfo: TAllRouteInfo
271
+ context: RouterContext
241
272
  listeners: Listener[]
242
- location: Location
273
+ location: Location<TAllRouteInfo['fullSearchSchema']>
243
274
  navigateTimeout?: Timeout
244
275
  nextAction?: 'push' | 'replace'
245
- state: RouterState
276
+ state: RouterState<TAllRouteInfo['fullSearchSchema']>
246
277
  routeTree: Route<TAllRouteInfo, RouteInfo>
247
278
  routesById: RoutesById<TAllRouteInfo>
248
279
  navigationPromise: Promise<void>
249
- removeActionQueue: { action: Action; actionState: ActionState }[]
250
280
  startedLoadingAt: number
251
281
  resolveNavigation: () => void
252
282
  subscribe: (listener: Listener) => () => void
283
+ reset: () => void
253
284
  notify: () => void
254
285
  mount: () => () => void
255
286
  onFocus: () => void
@@ -259,7 +290,7 @@ export interface Router<
259
290
 
260
291
  buildNext: (opts: BuildNextOptions) => Location
261
292
  cancelMatches: () => void
262
- loadLocation: (next?: Location) => Promise<void>
293
+ load: (next?: Location) => Promise<void>
263
294
  matchCache: Record<string, MatchCacheEntry>
264
295
  cleanMatchCache: () => void
265
296
  getRoute: <TId extends keyof TAllRouteInfo['routeInfoById']>(
@@ -276,11 +307,13 @@ export interface Router<
276
307
  ) => RouteMatch[]
277
308
  loadMatches: (
278
309
  resolvedMatches: RouteMatch[],
279
- loaderOpts?: { withPending?: boolean } & (
310
+ loaderOpts?:
280
311
  | { preload: true; maxAge: number; gcMaxAge: number }
281
- | { preload?: false; maxAge?: never; gcMaxAge?: never }
282
- ),
312
+ | { preload?: false; maxAge?: never; gcMaxAge?: never },
283
313
  ) => Promise<void>
314
+ loadMatchData: (
315
+ routeMatch: RouteMatch<any, any>,
316
+ ) => Promise<Record<string, unknown>>
284
317
  invalidateRoute: (opts: MatchLocation) => void
285
318
  reload: () => Promise<void>
286
319
  resolvePath: (from: string, path: string) => string
@@ -303,6 +336,8 @@ export interface Router<
303
336
  >(
304
337
  opts: LinkOptions<TAllRouteInfo, TFrom, TTo>,
305
338
  ) => LinkInfo
339
+ dehydrateState: () => DehydratedRouterState
340
+ hydrateState: (state: DehydratedRouterState) => void
306
341
  __: {
307
342
  buildRouteTree: (
308
343
  routeConfig: RouteConfig,
@@ -320,13 +355,25 @@ export interface Router<
320
355
  }
321
356
 
322
357
  // Detect if we're in the DOM
323
- const isServer = Boolean(
324
- typeof window === 'undefined' || !window.document?.createElement,
325
- )
358
+ const isServer =
359
+ typeof window === 'undefined' || !window.document?.createElement
326
360
 
327
361
  // This is the default history object if none is defined
328
362
  const createDefaultHistory = () =>
329
- !isServer ? createBrowserHistory() : createMemoryHistory()
363
+ isServer ? createMemoryHistory() : createBrowserHistory()
364
+
365
+ function getInitialRouterState(): RouterState {
366
+ return {
367
+ status: 'idle',
368
+ location: null!,
369
+ matches: [],
370
+ actions: {},
371
+ loaders: {},
372
+ lastUpdated: Date.now(),
373
+ isFetching: false,
374
+ isPreloading: false,
375
+ }
376
+ }
330
377
 
331
378
  export function createRouter<
332
379
  TRouteConfig extends AnyRouteConfig = RouteConfig,
@@ -347,30 +394,26 @@ export function createRouter<
347
394
  }
348
395
 
349
396
  let router: Router<TRouteConfig, TAllRouteInfo> = {
397
+ types: undefined!,
398
+
399
+ // public api
350
400
  history,
351
401
  options: originalOptions,
352
402
  listeners: [],
353
- removeActionQueue: [],
354
403
  // Resolved after construction
404
+ context: {},
355
405
  basepath: '',
356
406
  routeTree: undefined!,
357
407
  routesById: {} as any,
358
408
  location: undefined!,
359
- allRouteInfo: undefined!,
360
409
  //
361
410
  navigationPromise: Promise.resolve(),
362
411
  resolveNavigation: () => {},
363
412
  matchCache: {},
364
- state: {
365
- status: 'idle',
366
- location: null!,
367
- matches: [],
368
- actions: {},
369
- loaders: {},
370
- loaderData: {} as any,
371
- lastUpdated: Date.now(),
372
- isFetching: false,
373
- isPreloading: false,
413
+ state: getInitialRouterState(),
414
+ reset: () => {
415
+ router.state = getInitialRouterState()
416
+ router.notify()
374
417
  },
375
418
  startedLoadingAt: Date.now(),
376
419
  subscribe: (listener: Listener): (() => void) => {
@@ -383,22 +426,71 @@ export function createRouter<
383
426
  return router.routesById[id]
384
427
  },
385
428
  notify: (): void => {
386
- router.state = {
387
- ...router.state,
388
- isFetching:
389
- router.state.status === 'loading' ||
390
- router.state.matches.some((d) => d.isFetching),
391
- isPreloading: Object.values(router.matchCache).some(
392
- (d) =>
393
- d.match.isFetching &&
394
- !router.state.matches.find((dd) => dd.matchId === d.match.matchId),
395
- ),
429
+ const isFetching =
430
+ router.state.status === 'loading' ||
431
+ router.state.matches.some((d) => d.isFetching)
432
+
433
+ const isPreloading = Object.values(router.matchCache).some(
434
+ (d) =>
435
+ d.match.isFetching &&
436
+ !router.state.matches.find((dd) => dd.matchId === d.match.matchId),
437
+ )
438
+
439
+ if (
440
+ router.state.isFetching !== isFetching ||
441
+ router.state.isPreloading !== isPreloading
442
+ ) {
443
+ router.state = {
444
+ ...router.state,
445
+ isFetching,
446
+ isPreloading,
447
+ }
396
448
  }
397
449
 
398
450
  cascadeLoaderData(router.state.matches)
399
451
  router.listeners.forEach((listener) => listener(router))
400
452
  },
401
453
 
454
+ dehydrateState: () => {
455
+ return {
456
+ ...pick(router.state, ['status', 'location', 'lastUpdated']),
457
+ matches: router.state.matches.map((match) =>
458
+ pick(match, [
459
+ 'matchId',
460
+ 'status',
461
+ 'routeLoaderData',
462
+ 'loaderData',
463
+ 'isInvalid',
464
+ 'invalidAt',
465
+ ]),
466
+ ),
467
+ }
468
+ },
469
+
470
+ hydrateState: (dehydratedState) => {
471
+ // Match the routes
472
+ const matches = router.matchRoutes(router.location.pathname, {
473
+ strictParseParams: true,
474
+ })
475
+
476
+ matches.forEach((match, index) => {
477
+ const dehydratedMatch = dehydratedState.matches[index]
478
+ invariant(
479
+ dehydratedMatch,
480
+ 'Oh no! Dehydrated route matches did not match the active state of the router 😬',
481
+ )
482
+ Object.assign(match, dehydratedMatch)
483
+ })
484
+
485
+ matches.forEach((match) => match.__.validate())
486
+
487
+ router.state = {
488
+ ...router.state,
489
+ ...dehydratedState,
490
+ matches,
491
+ }
492
+ },
493
+
402
494
  mount: () => {
403
495
  const next = router.__.buildLocation({
404
496
  to: '.',
@@ -410,14 +502,14 @@ export function createRouter<
410
502
  // to the current location. Otherwise, load the current location.
411
503
  if (next.href !== router.location.href) {
412
504
  router.__.commitLocation(next, true)
413
- } else {
414
- router.loadLocation()
415
505
  }
416
506
 
417
- const unsub = history.listen((event) => {
418
- router.loadLocation(
419
- router.__.parseLocation(event.location, router.location),
420
- )
507
+ if (!router.state.matches.length) {
508
+ router.load()
509
+ }
510
+
511
+ const unsub = router.history.listen((event) => {
512
+ router.load(router.__.parseLocation(event.location, router.location))
421
513
  })
422
514
 
423
515
  // addEventListener does not exist in React Native, but window does
@@ -430,17 +522,28 @@ export function createRouter<
430
522
 
431
523
  return () => {
432
524
  unsub()
433
- // Be sure to unsubscribe if a new handler is set
434
- window.removeEventListener('visibilitychange', router.onFocus)
435
- window.removeEventListener('focus', router.onFocus)
525
+ if (!isServer && window.removeEventListener) {
526
+ // Be sure to unsubscribe if a new handler is set
527
+ window.removeEventListener('visibilitychange', router.onFocus)
528
+ window.removeEventListener('focus', router.onFocus)
529
+ }
436
530
  }
437
531
  },
438
532
 
439
533
  onFocus: () => {
440
- router.loadLocation()
534
+ router.load()
441
535
  },
442
536
 
443
537
  update: (opts) => {
538
+ const newHistory = opts?.history !== router.history
539
+ if (!router.location || newHistory) {
540
+ if (opts?.history) {
541
+ router.history = opts.history
542
+ }
543
+ router.location = router.__.parseLocation(router.history.location)
544
+ router.state.location = router.location
545
+ }
546
+
444
547
  Object.assign(router.options, opts)
445
548
 
446
549
  const { basepath, routeConfig } = router.options
@@ -464,7 +567,7 @@ export function createRouter<
464
567
  })
465
568
  },
466
569
 
467
- loadLocation: async (next?: Location) => {
570
+ load: async (next?: Location) => {
468
571
  const id = Math.random()
469
572
  router.startedLoadingAt = id
470
573
 
@@ -473,40 +576,52 @@ export function createRouter<
473
576
  router.location = next
474
577
  }
475
578
 
476
- // Clear out old actions
477
- router.removeActionQueue.forEach(({ action, actionState }) => {
478
- if (router.state.currentAction === actionState) {
479
- router.state.currentAction = undefined
480
- }
481
- if (action.current === actionState) {
482
- action.current = undefined
483
- }
484
- })
485
- router.removeActionQueue = []
486
-
487
579
  // Cancel any pending matches
488
580
  router.cancelMatches()
489
581
 
490
582
  // Match the routes
491
- const matches = router.matchRoutes(location.pathname, {
583
+ const matches = router.matchRoutes(router.location.pathname, {
492
584
  strictParseParams: true,
493
585
  })
494
586
 
495
- router.state = {
496
- ...router.state,
497
- pending: {
587
+ // Check if each match middleware to see if the route can be accessed
588
+ try {
589
+ await Promise.all(
590
+ matches.map((match) =>
591
+ match.options.beforeLoad?.({
592
+ context: router.context,
593
+ }),
594
+ ),
595
+ )
596
+ } catch (err: any) {
597
+ if (err?.then) {
598
+ await new Promise(() => {})
599
+ }
600
+ throw err
601
+ }
602
+
603
+ if (typeof document !== 'undefined') {
604
+ router.state = {
605
+ ...router.state,
606
+ pending: {
607
+ matches: matches,
608
+ location: router.location,
609
+ },
610
+ status: 'loading',
611
+ }
612
+ } else {
613
+ router.state = {
614
+ ...router.state,
498
615
  matches: matches,
499
616
  location: router.location,
500
- },
501
- status: 'loading',
617
+ status: 'loading',
618
+ }
502
619
  }
503
620
 
504
621
  router.notify()
505
622
 
506
623
  // Load the matches
507
- await router.loadMatches(matches, {
508
- withPending: true,
509
- })
624
+ await router.loadMatches(matches)
510
625
 
511
626
  if (router.startedLoadingAt !== id) {
512
627
  // Ignore side-effects of match loading
@@ -526,6 +641,10 @@ export function createRouter<
526
641
  }
527
642
  })
528
643
 
644
+ const entering = matches.filter((d) => {
645
+ return !previousMatches.find((dd) => dd.matchId === d.matchId)
646
+ })
647
+
529
648
  const now = Date.now()
530
649
 
531
650
  exiting.forEach((d) => {
@@ -533,6 +652,7 @@ export function createRouter<
533
652
  params: d.params,
534
653
  search: d.routeSearch,
535
654
  })
655
+
536
656
  // Clear idle error states when match leaves
537
657
  if (d.status === 'error' && !d.isFetching) {
538
658
  d.status = 'idle'
@@ -557,29 +677,27 @@ export function createRouter<
557
677
  })
558
678
  })
559
679
 
560
- const entering = matches.filter((d) => {
561
- return !previousMatches.find((dd) => dd.matchId === d.matchId)
562
- })
563
-
564
680
  entering.forEach((d) => {
565
- d.__.onExit = d.options.onMatch?.({
681
+ d.__.onExit = d.options.onLoaded?.({
566
682
  params: d.params,
567
683
  search: d.search,
568
684
  })
569
685
  delete router.matchCache[d.matchId]
570
686
  })
571
687
 
572
- if (matches.some((d) => d.status === 'loading')) {
573
- router.notify()
574
- await Promise.all(
575
- matches.map((d) => d.__.loaderPromise || Promise.resolve()),
576
- )
577
- }
578
688
  if (router.startedLoadingAt !== id) {
579
689
  // Ignore side-effects of match loading
580
690
  return
581
691
  }
582
692
 
693
+ matches.forEach((match) => {
694
+ // Clear actions
695
+ if (match.action) {
696
+ match.action.current = undefined
697
+ match.action.submissions = []
698
+ }
699
+ })
700
+
583
701
  router.state = {
584
702
  ...router.state,
585
703
  location: router.location,
@@ -727,6 +845,7 @@ export function createRouter<
727
845
  existingMatches.find((d) => d.matchId === matchId) ||
728
846
  router.matchCache[matchId]?.match ||
729
847
  createRouteMatch(router, foundRoute, {
848
+ parentMatch,
730
849
  matchId,
731
850
  params,
732
851
  pathname: joinPaths([pathname, interpolatedPath]),
@@ -755,12 +874,14 @@ export function createRouter<
755
874
  match.__.validate()
756
875
  match.load(loaderOpts)
757
876
 
758
- if (match.status === 'loading') {
759
- // If requested, start the pending timers
760
- if (loaderOpts?.withPending) match.__.startPending()
877
+ const search = match.search as { __data?: any }
878
+
879
+ if (search.__data && search.__data.matchId !== match.matchId) {
880
+ return
881
+ }
761
882
 
883
+ if (match.__.loadPromise) {
762
884
  // Wait for the first sign of activity from the match
763
- // This might be completion, error, or a pending state
764
885
  await match.__.loadPromise
765
886
  }
766
887
  })
@@ -770,6 +891,40 @@ export function createRouter<
770
891
  await Promise.all(matchPromises)
771
892
  },
772
893
 
894
+ loadMatchData: async (routeMatch) => {
895
+ if (isServer || !router.options.useServerData) {
896
+ return (
897
+ (await routeMatch.options.loader?.({
898
+ // parentLoaderPromise: routeMatch.parentMatch?.__.dataPromise,
899
+ params: routeMatch.params,
900
+ search: routeMatch.routeSearch,
901
+ signal: routeMatch.__.abortController.signal,
902
+ })) ?? {}
903
+ )
904
+ } else {
905
+ const next = router.buildNext({
906
+ to: '.',
907
+ search: (d: any) => ({
908
+ ...(d ?? {}),
909
+ __data: {
910
+ matchId: routeMatch.matchId,
911
+ },
912
+ }),
913
+ })
914
+
915
+ const res = await fetch(next.href, {
916
+ method: 'GET',
917
+ // signal: routeMatch.__.abortController.signal,
918
+ })
919
+
920
+ if (res.ok) {
921
+ return res.json()
922
+ }
923
+
924
+ throw new Error('Failed to fetch match data')
925
+ }
926
+ },
927
+
773
928
  invalidateRoute: (opts: MatchLocation) => {
774
929
  const next = router.buildNext(opts)
775
930
  const unloadedMatchIds = router
@@ -1012,12 +1167,6 @@ export function createRouter<
1012
1167
  return routeConfigs.map((routeConfig) => {
1013
1168
  const routeOptions = routeConfig.options
1014
1169
  const route = createRoute(routeConfig, routeOptions, parent, router)
1015
-
1016
- // {
1017
- // pendingMs: routeOptions.pendingMs ?? router.defaultPendingMs,
1018
- // pendingMinMs: routeOptions.pendingMinMs ?? router.defaultPendingMinMs,
1019
- // }
1020
-
1021
1170
  const existingRoute = (router.routesById as any)[route.routeId]
1022
1171
 
1023
1172
  if (existingRoute) {
@@ -1183,6 +1332,7 @@ export function createRouter<
1183
1332
  },
1184
1333
  {
1185
1334
  id,
1335
+ ...next.state,
1186
1336
  },
1187
1337
  )
1188
1338
  } else {
@@ -1212,9 +1362,6 @@ export function createRouter<
1212
1362
  },
1213
1363
  }
1214
1364
 
1215
- router.location = router.__.parseLocation(history.location)
1216
- router.state.location = router.location
1217
-
1218
1365
  router.update(userOptions)
1219
1366
 
1220
1367
  // Allow frameworks to hook into the router creation
@@ -1226,3 +1373,16 @@ export function createRouter<
1226
1373
  function isCtrlEvent(e: MouseEvent) {
1227
1374
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
1228
1375
  }
1376
+
1377
+ function cascadeLoaderData(matches: RouteMatch<any, any>[]) {
1378
+ matches.forEach((match, index) => {
1379
+ const parent = matches[index - 1]
1380
+
1381
+ if (parent) {
1382
+ match.loaderData = replaceEqualDeep(match.loaderData, {
1383
+ ...parent.loaderData,
1384
+ ...match.routeLoaderData,
1385
+ })
1386
+ }
1387
+ })
1388
+ }
package/src/utils.ts CHANGED
@@ -24,11 +24,11 @@ export type Expand<T> = T extends object
24
24
  : never
25
25
  : T
26
26
 
27
- // type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
28
- // k: infer I,
29
- // ) => any
30
- // ? I
31
- // : never
27
+ export type UnionToIntersection<U> = (
28
+ U extends any ? (k: U) => void : never
29
+ ) extends (k: infer I) => any
30
+ ? I
31
+ : never
32
32
 
33
33
  export type Values<O> = O[ValueKeys<O>]
34
34
  export type ValueKeys<O> = Extract<keyof O, PropertyKey>
@@ -155,3 +155,10 @@ export function functionalUpdate<TResult>(
155
155
 
156
156
  return updater
157
157
  }
158
+
159
+ export function pick<T, K extends keyof T>(parent: T, keys: K[]): Pick<T, K> {
160
+ return keys.reduce((obj: any, key: K) => {
161
+ obj[key] = parent[key]
162
+ return obj
163
+ }, {} as any)
164
+ }