@tanstack/react-router 0.0.1-beta.234 → 0.0.1-beta.236

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.234",
4
+ "version": "0.0.1-beta.236",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -40,9 +40,11 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@babel/runtime": "^7.16.7",
43
+ "@tanstack/react-store": "^0.2.1",
44
+ "@tanstack/store": "^0.1.3",
43
45
  "tiny-invariant": "^1.3.1",
44
46
  "tiny-warning": "^1.0.3",
45
- "@tanstack/history": "0.0.1-beta.234"
47
+ "@tanstack/history": "0.0.1-beta.236"
46
48
  },
47
49
  "scripts": {
48
50
  "build": "rollup --config rollup.config.js"
package/src/Matches.tsx CHANGED
@@ -47,11 +47,12 @@ export interface RouteMatch<
47
47
  export type AnyRouteMatch = RouteMatch<any>
48
48
 
49
49
  export function Matches() {
50
- const { routesById, state } = useRouter()
51
- const { matches } = state
52
-
50
+ const { routesById } = useRouter()
51
+ const routerState = useRouterState()
52
+ const matches = routerState.pendingMatches?.some((d) => d.showPending)
53
+ ? routerState.pendingMatches
54
+ : routerState.matches
53
55
  const locationKey = useRouterState().location.state.key
54
-
55
56
  const route = routesById[rootRouteId]!
56
57
 
57
58
  const errorComponent = React.useCallback(
@@ -307,9 +308,13 @@ export function useMatch<
307
308
 
308
309
  const matchRouteId = useRouterState({
309
310
  select: (state) => {
311
+ const matches = state.pendingMatches?.some((d) => d.showPending)
312
+ ? state.pendingMatches
313
+ : state.matches
314
+
310
315
  const match = opts?.from
311
- ? state.matches.find((d) => d.routeId === opts?.from)
312
- : state.matches.find((d) => d.id === nearestMatch.id)
316
+ ? matches.find((d) => d.routeId === opts?.from)
317
+ : matches.find((d) => d.id === nearestMatch.id)
313
318
 
314
319
  return match!.routeId
315
320
  },
@@ -330,9 +335,13 @@ export function useMatch<
330
335
 
331
336
  const matchSelection = useRouterState({
332
337
  select: (state) => {
338
+ const matches = state.pendingMatches?.some((d) => d.showPending)
339
+ ? state.pendingMatches
340
+ : state.matches
341
+
333
342
  const match = opts?.from
334
- ? state.matches.find((d) => d.routeId === opts?.from)
335
- : state.matches.find((d) => d.id === nearestMatch.id)
343
+ ? matches.find((d) => d.routeId === opts?.from)
344
+ : matches.find((d) => d.id === nearestMatch.id)
336
345
 
337
346
  invariant(
338
347
  match,
@@ -359,8 +368,12 @@ export function useMatches<T = RouteMatch[]>(opts?: {
359
368
 
360
369
  return useRouterState({
361
370
  select: (state) => {
362
- const matches = state.matches.slice(
363
- state.matches.findIndex((d) => d.id === contextMatches[0]?.id),
371
+ let matches = state.pendingMatches?.some((d) => d.showPending)
372
+ ? state.pendingMatches
373
+ : state.matches
374
+
375
+ matches = matches.slice(
376
+ matches.findIndex((d) => d.id === contextMatches[0]?.id),
364
377
  )
365
378
  return opts?.select ? opts.select(matches) : (matches as T)
366
379
  },
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react'
2
2
  import warning from 'tiny-warning'
3
+ import { useStore } from '@tanstack/react-store'
3
4
  import { Matches } from './Matches'
4
5
  import {
5
6
  LinkInfo,
@@ -99,39 +100,33 @@ function RouterProviderInner<
99
100
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
100
101
  TDehydrated extends Record<string, any> = Record<string, any>,
101
102
  >({ router }: RouterProps<TRouteTree, TDehydrated>) {
102
- const [preState, setState] = React.useState(() => router.state)
103
103
  const [isTransitioning, startReactTransition] = React.useTransition()
104
- const isAnyTransitioning =
105
- isTransitioning || preState.matches.some((d) => d.status === 'pending')
106
-
107
- const state = React.useMemo<RouterState<TRouteTree>>(
108
- () => ({
109
- ...preState,
110
- status: isAnyTransitioning ? 'pending' : 'idle',
111
- location: isTransitioning ? router.latestLocation : preState.location,
112
- pendingMatches: router.pendingMatches,
113
- }),
114
- [preState, isTransitioning],
115
- )
116
104
 
117
- router.setState = setState
118
- router.state = state
119
105
  router.startReactTransition = startReactTransition
120
106
 
107
+ React.useEffect(() => {
108
+ if (isTransitioning) {
109
+ router.__store.setState((s) => ({
110
+ ...s,
111
+ isTransitioning,
112
+ }))
113
+ }
114
+ }, [isTransitioning])
115
+
121
116
  const tryLoad = () => {
122
- startReactTransition(() => {
123
- try {
124
- router.load()
125
- } catch (err) {
126
- console.error(err)
127
- }
128
- })
117
+ // startReactTransition(() => {
118
+ try {
119
+ router.load()
120
+ } catch (err) {
121
+ console.error(err)
122
+ }
123
+ // })
129
124
  }
130
125
 
131
126
  useLayoutEffect(() => {
132
127
  const unsub = router.history.subscribe(() => {
133
128
  router.latestLocation = router.parseLocation(router.latestLocation)
134
- if (state.location !== router.latestLocation) {
129
+ if (router.state.location !== router.latestLocation) {
135
130
  tryLoad()
136
131
  }
137
132
  })
@@ -143,7 +138,7 @@ function RouterProviderInner<
143
138
  state: true,
144
139
  })
145
140
 
146
- if (state.location.href !== nextLocation.href) {
141
+ if (router.state.location.href !== nextLocation.href) {
147
142
  router.commitLocation({ ...nextLocation, replace: true })
148
143
  }
149
144
 
@@ -153,21 +148,26 @@ function RouterProviderInner<
153
148
  }, [router.history])
154
149
 
155
150
  useLayoutEffect(() => {
156
- if (!isTransitioning && state.resolvedLocation !== state.location) {
151
+ if (
152
+ !isTransitioning &&
153
+ router.state.resolvedLocation !== router.state.location
154
+ ) {
157
155
  router.emit({
158
156
  type: 'onResolved',
159
- fromLocation: state.resolvedLocation,
160
- toLocation: state.location,
161
- pathChanged: state.location!.href !== state.resolvedLocation?.href,
157
+ fromLocation: router.state.resolvedLocation,
158
+ toLocation: router.state.location,
159
+ pathChanged:
160
+ router.state.location!.href !== router.state.resolvedLocation?.href,
162
161
  })
163
162
  router.pendingMatches = []
164
163
 
165
- setState((s) => ({
164
+ router.__store.setState((s) => ({
166
165
  ...s,
166
+ isTransitioning: false,
167
167
  resolvedLocation: s.location,
168
168
  }))
169
169
  }
170
- })
170
+ }, [isTransitioning])
171
171
 
172
172
  useLayoutEffect(() => {
173
173
  if (!window.__TSR_DEHYDRATED__) {
@@ -186,7 +186,9 @@ export function getRouteMatch<TRouteTree extends AnyRoute>(
186
186
  state: RouterState<TRouteTree>,
187
187
  id: string,
188
188
  ): undefined | RouteMatch<TRouteTree> {
189
- return [...state.pendingMatches, ...state.matches].find((d) => d.id === id)
189
+ return [...(state.pendingMatches ?? []), ...state.matches].find(
190
+ (d) => d.id === id,
191
+ )
190
192
  }
191
193
 
192
194
  export function useRouterState<
@@ -194,9 +196,8 @@ export function useRouterState<
194
196
  >(opts?: {
195
197
  select: (state: RouterState<RegisteredRouter['routeTree']>) => TSelected
196
198
  }): TSelected {
197
- const { state } = useRouter()
198
- // return useStore(router.__store, opts?.select as any)
199
- return opts?.select ? opts.select(state) : (state as any)
199
+ const router = useRouter()
200
+ return useStore(router.__store, opts?.select as any)
200
201
  }
201
202
 
202
203
  export type RouterProps<
package/src/router.ts CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  createBrowserHistory,
6
6
  createMemoryHistory,
7
7
  } from '@tanstack/history'
8
+ import { Store } from '@tanstack/store'
8
9
 
9
10
  //
10
11
 
@@ -29,7 +30,6 @@ import {
29
30
  functionalUpdate,
30
31
  last,
31
32
  pick,
32
- PickAsPartial,
33
33
  } from './utils'
34
34
  import {
35
35
  ErrorRouteComponent,
@@ -134,8 +134,10 @@ export interface RouterOptions<
134
134
 
135
135
  export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
136
136
  status: 'pending' | 'idle'
137
+ isLoading: boolean
138
+ isTransitioning: boolean
137
139
  matches: RouteMatch<TRouteTree>[]
138
- pendingMatches: RouteMatch<TRouteTree>[]
140
+ pendingMatches?: RouteMatch<TRouteTree>[]
139
141
  location: ParsedLocation<FullSearchSchema<TRouteTree>>
140
142
  resolvedLocation: ParsedLocation<FullSearchSchema<TRouteTree>>
141
143
  lastUpdated: number
@@ -236,7 +238,7 @@ export class Router<
236
238
  dehydratedData?: TDehydrated
237
239
 
238
240
  // Must build in constructor
239
- state!: RouterState<TRouteTree>
241
+ __store!: Store<RouterState<TRouteTree>>
240
242
  options!: PickAsRequired<
241
243
  RouterOptions<TRouteTree, TDehydrated>,
242
244
  'stringifySearch' | 'parseSearch' | 'context'
@@ -265,11 +267,6 @@ export class Router<
265
267
  // by the router provider once rendered. We provide these so that the
266
268
  // router can be used in a non-react environment if necessary
267
269
  startReactTransition: (fn: () => void) => void = (fn) => fn()
268
- setState: (updater: NonNullableUpdater<RouterState<TRouteTree>>) => void = (
269
- updater,
270
- ) => {
271
- this.state = functionalUpdate(updater, this.state)
272
- }
273
270
 
274
271
  update = (newOptions: RouterConstructorOptions<TRouteTree, TDehydrated>) => {
275
272
  this.options = {
@@ -296,11 +293,25 @@ export class Router<
296
293
  this.buildRouteTree()
297
294
  }
298
295
 
299
- if (!this.state) {
300
- this.state = getInitialRouterState(this.latestLocation)
296
+ if (!this.__store) {
297
+ this.__store = new Store(getInitialRouterState(this.latestLocation), {
298
+ onUpdate: () => {
299
+ this.__store.state = {
300
+ ...this.state,
301
+ status:
302
+ this.state.isTransitioning || this.state.isLoading
303
+ ? 'pending'
304
+ : 'idle',
305
+ }
306
+ },
307
+ })
301
308
  }
302
309
  }
303
310
 
311
+ get state() {
312
+ return this.__store.state
313
+ }
314
+
304
315
  buildRouteTree = () => {
305
316
  this.routesById = {} as RoutesById<TRouteTree>
306
317
  this.routesByPath = {} as RoutesByPath<TRouteTree>
@@ -654,7 +665,7 @@ export class Router<
654
665
  }
655
666
 
656
667
  cancelMatches = () => {
657
- this.state.matches.forEach((match) => {
668
+ this.state.pendingMatches?.forEach((match) => {
658
669
  this.cancelMatch(match.id)
659
670
  })
660
671
  }
@@ -948,6 +959,15 @@ export class Router<
948
959
  let latestPromise
949
960
  let firstBadMatchIndex: number | undefined
950
961
 
962
+ const updatePendingMatch = (match: AnyRouteMatch) => {
963
+ this.__store.setState((s) => ({
964
+ ...s,
965
+ pendingMatches: s.pendingMatches?.map((d) =>
966
+ d.id === match.id ? match : d,
967
+ ),
968
+ }))
969
+ }
970
+
951
971
  // Check each match middleware to see if the route can be accessed
952
972
  try {
953
973
  for (let [index, match] of matches.entries()) {
@@ -1149,13 +1169,12 @@ export class Router<
1149
1169
  }
1150
1170
 
1151
1171
  if (!preload) {
1152
- this.setState((s) => ({
1153
- ...s,
1154
- matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1155
- }))
1172
+ updatePendingMatch(match)
1156
1173
  }
1157
1174
 
1158
1175
  let didShowPending = false
1176
+ const pendingMinMs =
1177
+ route.options.pendingMinMs ?? this.options.defaultPendingMinMs
1159
1178
 
1160
1179
  await new Promise<void>(async (resolve) => {
1161
1180
  // If the route has a pending component and a pendingMs option,
@@ -1170,12 +1189,7 @@ export class Router<
1170
1189
  showPending: true,
1171
1190
  }
1172
1191
 
1173
- this.setState((s) => ({
1174
- ...s,
1175
- matches: s.matches.map((d) =>
1176
- d.id === match.id ? match : d,
1177
- ),
1178
- }))
1192
+ updatePendingMatch(match)
1179
1193
  resolve()
1180
1194
  })
1181
1195
  }
@@ -1184,9 +1198,6 @@ export class Router<
1184
1198
  const loaderData = await loadPromise
1185
1199
  if ((latestPromise = checkLatest())) return await latestPromise
1186
1200
 
1187
- const pendingMinMs =
1188
- route.options.pendingMinMs ?? this.options.defaultPendingMinMs
1189
-
1190
1201
  if (didShowPending && pendingMinMs) {
1191
1202
  await new Promise((r) => setTimeout(r, pendingMinMs))
1192
1203
  }
@@ -1220,13 +1231,22 @@ export class Router<
1220
1231
  isFetching: false,
1221
1232
  updatedAt: Date.now(),
1222
1233
  }
1234
+ } finally {
1235
+ // If we showed the pending component, that means
1236
+ // we already moved the pendingMatches to the matches
1237
+ // state, so we need to update that specific match
1238
+ if (didShowPending && pendingMinMs && match.showPending) {
1239
+ this.__store.setState((s) => ({
1240
+ ...s,
1241
+ matches: s.matches?.map((d) =>
1242
+ d.id === match.id ? match : d,
1243
+ ),
1244
+ }))
1245
+ }
1223
1246
  }
1224
1247
 
1225
1248
  if (!preload) {
1226
- this.setState((s) => ({
1227
- ...s,
1228
- matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1229
- }))
1249
+ updatePendingMatch(match)
1230
1250
  }
1231
1251
 
1232
1252
  resolve()
@@ -1262,7 +1282,7 @@ export class Router<
1262
1282
  })
1263
1283
 
1264
1284
  // Match the routes
1265
- let matches: RouteMatch<any, any>[] = this.matchRoutes(
1285
+ let pendingMatches: RouteMatch<any, any>[] = this.matchRoutes(
1266
1286
  next.pathname,
1267
1287
  next.search,
1268
1288
  {
@@ -1270,23 +1290,21 @@ export class Router<
1270
1290
  },
1271
1291
  )
1272
1292
 
1273
- this.pendingMatches = matches
1274
-
1275
1293
  const previousMatches = this.state.matches
1276
1294
 
1277
1295
  // Ingest the new matches
1278
- this.setState((s) => ({
1296
+ this.__store.setState((s) => ({
1279
1297
  ...s,
1280
- // status: 'pending',
1298
+ isLoading: true,
1281
1299
  location: next,
1282
- matches,
1300
+ pendingMatches,
1283
1301
  }))
1284
1302
 
1285
1303
  try {
1286
1304
  try {
1287
1305
  // Load the matches
1288
1306
  await this.loadMatches({
1289
- matches,
1307
+ matches: pendingMatches,
1290
1308
  checkLatest: () => this.checkLatest(promise),
1291
1309
  invalidate: opts?.invalidate,
1292
1310
  })
@@ -1310,12 +1328,12 @@ export class Router<
1310
1328
  this.pendingMatches.includes(id),
1311
1329
  )
1312
1330
 
1313
- // setState((s) => ({
1314
- // ...s,
1315
- // status: 'idle',
1316
- // resolvedLocation: s.location,
1317
- // matches,
1318
- // }))
1331
+ this.__store.setState((s) => ({
1332
+ ...s,
1333
+ isLoading: false,
1334
+ matches: pendingMatches,
1335
+ pendingMatches: undefined,
1336
+ }))
1319
1337
 
1320
1338
  //
1321
1339
  ;(
@@ -1517,8 +1535,6 @@ export class Router<
1517
1535
  ? this.latestLocation
1518
1536
  : this.state.resolvedLocation
1519
1537
 
1520
- // const baseLocation = state.resolvedLocation
1521
-
1522
1538
  if (!baseLocation) {
1523
1539
  return false
1524
1540
  }
@@ -1633,7 +1649,7 @@ export class Router<
1633
1649
  return match
1634
1650
  })
1635
1651
 
1636
- this.setState((s) => {
1652
+ this.__store.setState((s) => {
1637
1653
  return {
1638
1654
  ...s,
1639
1655
  matches: matches as any,
@@ -1672,6 +1688,8 @@ export function getInitialRouterState(
1672
1688
  location: ParsedLocation,
1673
1689
  ): RouterState<any> {
1674
1690
  return {
1691
+ isLoading: false,
1692
+ isTransitioning: false,
1675
1693
  status: 'idle',
1676
1694
  resolvedLocation: location,
1677
1695
  location,