@tanstack/react-router 0.0.1-beta.206 → 0.0.1-beta.208

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.206",
4
+ "version": "0.0.1-beta.208",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -42,7 +42,7 @@
42
42
  "@babel/runtime": "^7.16.7",
43
43
  "tiny-invariant": "^1.3.1",
44
44
  "tiny-warning": "^1.0.3",
45
- "@tanstack/history": "0.0.1-beta.206"
45
+ "@tanstack/history": "0.0.1-beta.208"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "rollup --config rollup.config.js"
@@ -134,6 +134,10 @@ export type RouterContext<
134
134
 
135
135
  export const routerContext = React.createContext<RouterContext<any>>(null!)
136
136
 
137
+ if (typeof document !== 'undefined') {
138
+ window.__TSR_ROUTER_CONTEXT__ = routerContext as any
139
+ }
140
+
137
141
  export function getInitialRouterState(
138
142
  location: ParsedLocation,
139
143
  ): RouterState<any> {
@@ -175,6 +179,14 @@ export function RouterProvider<
175
179
 
176
180
  const navigateTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
177
181
 
182
+ const latestLoadPromiseRef = React.useRef<Promise<void>>(Promise.resolve())
183
+
184
+ const checkLatest = (promise: Promise<void>): undefined | Promise<void> => {
185
+ return latestLoadPromiseRef.current !== promise
186
+ ? latestLoadPromiseRef.current
187
+ : undefined
188
+ }
189
+
178
190
  const parseLocation = useStableCallback(
179
191
  (
180
192
  previousLocation?: ParsedLocation,
@@ -346,8 +358,6 @@ export function RouterProvider<
346
358
  [routesByPath],
347
359
  )
348
360
 
349
- const latestLoadPromiseRef = React.useRef<Promise<void>>(Promise.resolve())
350
-
351
361
  const matchRoutes = useStableCallback(
352
362
  <TRouteTree extends AnyRoute>(
353
363
  pathname: string,
@@ -786,12 +796,15 @@ export function RouterProvider<
786
796
 
787
797
  const loadMatches = useStableCallback(
788
798
  async ({
799
+ checkLatest,
789
800
  matches,
790
801
  preload,
791
802
  }: {
803
+ checkLatest: () => Promise<void> | undefined
792
804
  matches: AnyRouteMatch[]
793
805
  preload?: boolean
794
- }) => {
806
+ }): Promise<RouteMatch[]> => {
807
+ let latestPromise
795
808
  let firstBadMatchIndex: number | undefined
796
809
 
797
810
  // Check each match middleware to see if the route can be accessed
@@ -845,6 +858,7 @@ export function RouterProvider<
845
858
  preload: !!preload,
846
859
  meta: parentMeta,
847
860
  location: state.location, // TODO: This might need to be latestLocationRef.current...?
861
+ navigate: (opts) => navigate({ ...opts, from: match.pathname }),
848
862
  })) ?? ({} as any)
849
863
 
850
864
  const meta = {
@@ -864,7 +878,7 @@ export function RouterProvider<
864
878
  } catch (err) {
865
879
  if (isRedirect(err)) {
866
880
  if (!preload) navigate(err as any)
867
- return
881
+ return matches
868
882
  }
869
883
 
870
884
  throw err
@@ -883,14 +897,6 @@ export function RouterProvider<
883
897
  return getRouteMatch(state, match.id)?.loadPromise
884
898
  }
885
899
 
886
- const fetchedAt = Date.now()
887
- const checkLatest = () => {
888
- const latest = getRouteMatch(state, match.id)
889
- return latest && latest.fetchedAt !== fetchedAt
890
- ? latest.loadPromise
891
- : undefined
892
- }
893
-
894
900
  const handleIfRedirect = (err: any) => {
895
901
  if (isRedirect(err)) {
896
902
  if (!preload) {
@@ -902,8 +908,6 @@ export function RouterProvider<
902
908
  }
903
909
 
904
910
  const load = async () => {
905
- let latestPromise
906
-
907
911
  try {
908
912
  const componentsPromise = Promise.all(
909
913
  componentTypes.map(async (type) => {
@@ -922,6 +926,9 @@ export function RouterProvider<
922
926
  parentMatchPromise,
923
927
  abortController: match.abortController,
924
928
  meta: match.meta,
929
+ location: state.location,
930
+ navigate: (opts) =>
931
+ navigate({ ...opts, from: match.pathname }),
925
932
  })
926
933
 
927
934
  await Promise.all([componentsPromise, loaderPromise])
@@ -953,6 +960,15 @@ export function RouterProvider<
953
960
  updatedAt: Date.now(),
954
961
  }
955
962
  }
963
+
964
+ if (!preload) {
965
+ setState((s) => ({
966
+ ...s,
967
+ matches: s.matches.map((d) =>
968
+ d.id === match.id ? match : d,
969
+ ),
970
+ }))
971
+ }
956
972
  }
957
973
 
958
974
  let loadPromise: Promise<void> | undefined
@@ -960,7 +976,7 @@ export function RouterProvider<
960
976
  matches[index] = match = {
961
977
  ...match,
962
978
  isFetching: true,
963
- fetchedAt,
979
+ fetchedAt: Date.now(),
964
980
  invalid: false,
965
981
  }
966
982
 
@@ -977,48 +993,38 @@ export function RouterProvider<
977
993
  })
978
994
 
979
995
  await Promise.all(matchPromises)
996
+ return matches
980
997
  },
981
998
  )
982
999
 
983
- const load = useStableCallback<LoadFn>(async (opts) => {
1000
+ const load = useStableCallback<LoadFn>(async () => {
984
1001
  const promise = new Promise<void>(async (resolve, reject) => {
1002
+ const next = latestLocationRef.current
985
1003
  const prevLocation = state.resolvedLocation
986
- const pathDidChange = !!(
987
- opts?.next && prevLocation!.href !== opts.next.href
988
- )
989
-
1004
+ const pathDidChange = !!(next && prevLocation!.href !== next.href)
990
1005
  let latestPromise: Promise<void> | undefined | null
991
1006
 
992
- const checkLatest = (): undefined | Promise<void> | null => {
993
- return latestLoadPromiseRef.current !== promise
994
- ? latestLoadPromiseRef.current
995
- : undefined
996
- }
997
-
998
1007
  // Cancel any pending matches
999
1008
  cancelMatches(state)
1000
1009
 
1001
1010
  router.emit({
1002
1011
  type: 'onBeforeLoad',
1003
1012
  from: prevLocation,
1004
- to: opts?.next ?? state.location,
1013
+ to: next ?? state.location,
1005
1014
  pathChanged: pathDidChange,
1006
1015
  })
1007
1016
 
1008
- if (opts?.next) {
1009
- // Ingest the new location
1010
- setState((s) => ({
1011
- ...s,
1012
- location: opts.next! as any,
1013
- }))
1014
- }
1017
+ // Ingest the new location
1018
+ setState((s) => ({
1019
+ ...s,
1020
+ location: next,
1021
+ }))
1015
1022
 
1016
1023
  // Match the routes
1017
- const matches: RouteMatch<any, any>[] = matchRoutes(
1018
- state.location.pathname,
1019
- state.location.search,
1024
+ let matches: RouteMatch<any, any>[] = matchRoutes(
1025
+ next.pathname,
1026
+ next.search,
1020
1027
  {
1021
- throwOnError: opts?.throwOnError,
1022
1028
  debug: true,
1023
1029
  },
1024
1030
  )
@@ -1030,10 +1036,11 @@ export function RouterProvider<
1030
1036
  }))
1031
1037
 
1032
1038
  try {
1033
- // Load the matches
1034
1039
  try {
1040
+ // Load the matches
1035
1041
  await loadMatches({
1036
1042
  matches,
1043
+ checkLatest: () => checkLatest(promise),
1037
1044
  })
1038
1045
  } catch (err) {
1039
1046
  // swallow this error, since we'll display the
@@ -1041,7 +1048,7 @@ export function RouterProvider<
1041
1048
  }
1042
1049
 
1043
1050
  // Only apply the latest transition
1044
- if ((latestPromise = checkLatest())) {
1051
+ if ((latestPromise = checkLatest(promise))) {
1045
1052
  return latestPromise
1046
1053
  }
1047
1054
 
@@ -1078,14 +1085,14 @@ export function RouterProvider<
1078
1085
  router.emit({
1079
1086
  type: 'onLoad',
1080
1087
  from: prevLocation,
1081
- to: state.location,
1088
+ to: next,
1082
1089
  pathChanged: pathDidChange,
1083
1090
  })
1084
1091
 
1085
1092
  resolve()
1086
1093
  } catch (err) {
1087
1094
  // Only apply the latest transition
1088
- if ((latestPromise = checkLatest())) {
1095
+ if ((latestPromise = checkLatest(promise))) {
1089
1096
  return latestPromise
1090
1097
  }
1091
1098
 
@@ -1098,14 +1105,6 @@ export function RouterProvider<
1098
1105
  return latestLoadPromiseRef.current
1099
1106
  })
1100
1107
 
1101
- const safeLoad = React.useCallback(async () => {
1102
- try {
1103
- return load()
1104
- } catch (err) {
1105
- // Don't do anything
1106
- }
1107
- }, [])
1108
-
1109
1108
  const preloadRoute = useStableCallback(
1110
1109
  async (navigateOpts: BuildNextOptions = state.location) => {
1111
1110
  let next = buildLocation(navigateOpts)
@@ -1117,6 +1116,7 @@ export function RouterProvider<
1117
1116
  await loadMatches({
1118
1117
  matches,
1119
1118
  preload: true,
1119
+ checkLatest: () => undefined,
1120
1120
  })
1121
1121
 
1122
1122
  return [last(matches)!, matches] as const
@@ -1259,10 +1259,13 @@ export function RouterProvider<
1259
1259
  latestLocationRef.current = parseLocation(latestLocationRef.current)
1260
1260
 
1261
1261
  React.startTransition(() => {
1262
- setState((s) => ({
1263
- ...s,
1264
- location: latestLocationRef.current,
1265
- }))
1262
+ if (state.location !== latestLocationRef.current) {
1263
+ try {
1264
+ load()
1265
+ } catch (err) {
1266
+ console.error(err)
1267
+ }
1268
+ }
1266
1269
  })
1267
1270
  })
1268
1271
 
@@ -1286,14 +1289,12 @@ export function RouterProvider<
1286
1289
 
1287
1290
  if (initialLoad.current) {
1288
1291
  initialLoad.current = false
1289
- safeLoad()
1290
- }
1291
-
1292
- React.useLayoutEffect(() => {
1293
- if (state.resolvedLocation !== state.location) {
1294
- safeLoad()
1292
+ try {
1293
+ load()
1294
+ } catch (err) {
1295
+ console.error(err)
1295
1296
  }
1296
- }, [state.location])
1297
+ }
1297
1298
 
1298
1299
  const isFetching = React.useMemo(
1299
1300
  () => [...state.matches, ...state.pendingMatches].some((d) => d.isFetching),
package/src/react.tsx CHANGED
@@ -404,7 +404,8 @@ export type RouterProps<
404
404
  export function useRouter<
405
405
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
406
406
  >(): RouterContext<TRouteTree> {
407
- const value = React.useContext(routerContext)
407
+ const resolvedContext = window.__TSR_ROUTER_CONTEXT__ || routerContext
408
+ const value = React.useContext(resolvedContext)
408
409
  warning(value, 'useRouter must be used inside a <RouterProvider> component!')
409
410
  return value as any
410
411
  }
@@ -597,6 +598,20 @@ export function useNavigate<
597
598
  )
598
599
  }
599
600
 
601
+ export function typedNavigate<
602
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
603
+ TDefaultFrom extends RoutePaths<TRouteTree> = '/',
604
+ >(navigate: (opts: NavigateOptions<any>) => Promise<void>) {
605
+ return navigate as <
606
+ TFrom extends RoutePaths<TRouteTree> = TDefaultFrom,
607
+ TTo extends string = '',
608
+ TMaskFrom extends RoutePaths<TRouteTree> = '/',
609
+ TMaskTo extends string = '',
610
+ >(
611
+ opts?: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
612
+ ) => Promise<void>
613
+ }
614
+
600
615
  export function useMatchRoute<
601
616
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
602
617
  >() {
package/src/route.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  UnionToIntersection,
12
12
  Assign,
13
13
  } from './utils'
14
- import { ParsePathParams, ToSubOptions } from './link'
14
+ import { NavigateOptions, ParsePathParams, ToSubOptions } from './link'
15
15
  import {
16
16
  ErrorRouteComponent,
17
17
  PendingRouteComponent,
@@ -154,6 +154,7 @@ type BeforeLoadFn<
154
154
  params: TAllParams
155
155
  meta: TParentRoute['types']['allMeta']
156
156
  location: ParsedLocation
157
+ navigate: (opts: NavigateOptions<AnyRoute>) => Promise<void>
157
158
  }) => Promise<TRouteMeta> | TRouteMeta | void
158
159
 
159
160
  export type UpdatableRouteOptions<
@@ -265,6 +266,8 @@ export interface LoadFnContext<
265
266
  params: TAllParams
266
267
  search: TFullSearchSchema
267
268
  meta: Expand<Assign<TAllContext, TRouteMeta>>
269
+ location: ParsedLocation<TFullSearchSchema>
270
+ navigate: (opts: NavigateOptions<AnyRoute>) => Promise<void>
268
271
  }
269
272
 
270
273
  export type SearchFilter<T, U = T> = (prev: T) => U
package/src/router.ts CHANGED
@@ -21,12 +21,14 @@ import { RouteMatch } from './RouteMatch'
21
21
  import { ParsedLocation } from './location'
22
22
  import { LocationState } from './location'
23
23
  import { SearchSerializer, SearchParser } from './searchParams'
24
+ import { RouterContext } from './RouterProvider'
24
25
 
25
26
  //
26
27
 
27
28
  declare global {
28
29
  interface Window {
29
30
  __TSR_DEHYDRATED__?: HydrationCtx
31
+ __TSR_ROUTER_CONTEXT__?: React.Context<RouterContext<any>>
30
32
  }
31
33
  }
32
34