@tanstack/react-router 0.0.1-beta.231 → 0.0.1-beta.232

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.231",
4
+ "version": "0.0.1-beta.232",
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.231"
45
+ "@tanstack/history": "0.0.1-beta.232"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "rollup --config rollup.config.js"
@@ -18,7 +18,7 @@ import {
18
18
  RouterOptions,
19
19
  RouterState,
20
20
  } from './router'
21
- import { NoInfer, PickAsRequired } from './utils'
21
+ import { NoInfer, PickAsRequired, useLayoutEffect } from './utils'
22
22
  import { MatchRouteOptions } from './Matches'
23
23
  import { RouteMatch } from './Matches'
24
24
 
@@ -72,41 +72,33 @@ if (typeof document !== 'undefined') {
72
72
  window.__TSR_ROUTER_CONTEXT__ = routerContext as any
73
73
  }
74
74
 
75
- export class SearchParamError extends Error {}
76
-
77
- export class PathParamError extends Error {}
78
-
79
- export function getInitialRouterState(
80
- location: ParsedLocation,
81
- ): RouterState<any> {
82
- return {
83
- status: 'idle',
84
- resolvedLocation: location,
85
- location,
86
- matches: [],
87
- pendingMatches: [],
88
- lastUpdated: Date.now(),
89
- }
90
- }
91
-
92
75
  export function RouterProvider<
93
76
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
94
77
  TDehydrated extends Record<string, any> = Record<string, any>,
95
78
  >({ router, ...rest }: RouterProps<TRouteTree, TDehydrated>) {
96
79
  // Allow the router to update options on the router instance
97
- router.updateOptions({
80
+ router.update({
98
81
  ...router.options,
99
82
  ...rest,
100
-
101
83
  context: {
102
84
  ...router.options.context,
103
85
  ...rest?.context,
104
86
  },
105
- } as PickAsRequired<
106
- RouterOptions<TRouteTree, TDehydrated>,
107
- 'stringifySearch' | 'parseSearch' | 'context'
108
- >)
87
+ } as any)
88
+
89
+ const inner = <RouterProviderInner<TRouteTree, TDehydrated> router={router} />
90
+
91
+ if (router.options.Wrap) {
92
+ return <router.options.Wrap>{inner}</router.options.Wrap>
93
+ }
94
+
95
+ return inner
96
+ }
109
97
 
98
+ function RouterProviderInner<
99
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
100
+ TDehydrated extends Record<string, any> = Record<string, any>,
101
+ >({ router }: RouterProps<TRouteTree, TDehydrated>) {
110
102
  const [preState, setState] = React.useState(() => router.state)
111
103
  const [isTransitioning, startReactTransition] = React.useTransition()
112
104
  const isAnyTransitioning =
@@ -126,19 +118,22 @@ export function RouterProvider<
126
118
  router.state = state
127
119
  router.startReactTransition = startReactTransition
128
120
 
129
- React.useLayoutEffect(() => {
121
+ const tryLoad = () => {
122
+ if (state.location !== router.latestLocation) {
123
+ startReactTransition(() => {
124
+ try {
125
+ router.load()
126
+ } catch (err) {
127
+ console.error(err)
128
+ }
129
+ })
130
+ }
131
+ }
132
+
133
+ useLayoutEffect(() => {
130
134
  const unsub = router.history.subscribe(() => {
131
135
  router.latestLocation = router.parseLocation(router.latestLocation)
132
-
133
- if (state.location !== router.latestLocation) {
134
- startReactTransition(() => {
135
- try {
136
- router.load()
137
- } catch (err) {
138
- console.error(err)
139
- }
140
- })
141
- }
136
+ tryLoad()
142
137
  })
143
138
 
144
139
  const nextLocation = router.buildLocation({
@@ -157,7 +152,7 @@ export function RouterProvider<
157
152
  }
158
153
  }, [router.history])
159
154
 
160
- React.useLayoutEffect(() => {
155
+ useLayoutEffect(() => {
161
156
  if (!isTransitioning && state.resolvedLocation !== state.location) {
162
157
  router.emit({
163
158
  type: 'onResolved',
@@ -174,14 +169,10 @@ export function RouterProvider<
174
169
  }
175
170
  })
176
171
 
177
- React.useLayoutEffect(() => {
178
- startReactTransition(() => {
179
- try {
180
- router.load()
181
- } catch (err) {
182
- console.error(err)
183
- }
184
- })
172
+ useLayoutEffect(() => {
173
+ if (!window.__TSR_DEHYDRATED__) {
174
+ tryLoad()
175
+ }
185
176
  }, [])
186
177
 
187
178
  return (
@@ -219,7 +210,10 @@ export type RouterProps<
219
210
  export function useRouter<
220
211
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
221
212
  >(): Router<TRouteTree> {
222
- const resolvedContext = window.__TSR_ROUTER_CONTEXT__ || routerContext
213
+ const resolvedContext =
214
+ typeof document !== 'undefined'
215
+ ? window.__TSR_ROUTER_CONTEXT__ || routerContext
216
+ : routerContext
223
217
  const value = React.useContext(resolvedContext)
224
218
  warning(value, 'useRouter must be used inside a <RouterProvider> component!')
225
219
  return value as any
package/src/router.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  HistoryState,
4
4
  RouterHistory,
5
5
  createBrowserHistory,
6
+ createMemoryHistory,
6
7
  } from '@tanstack/history'
7
8
 
8
9
  //
@@ -28,6 +29,7 @@ import {
28
29
  functionalUpdate,
29
30
  last,
30
31
  pick,
32
+ PickAsPartial,
31
33
  } from './utils'
32
34
  import {
33
35
  ErrorRouteComponent,
@@ -45,11 +47,9 @@ import {
45
47
  InjectedHtmlEntry,
46
48
  MatchRouteFn,
47
49
  NavigateFn,
48
- PathParamError,
49
- SearchParamError,
50
- getInitialRouterState,
51
50
  getRouteMatch,
52
51
  } from './RouterProvider'
52
+
53
53
  import {
54
54
  cleanPath,
55
55
  interpolatePath,
@@ -62,7 +62,7 @@ import {
62
62
  } from './path'
63
63
  import invariant from 'tiny-invariant'
64
64
  import { isRedirect } from './redirects'
65
- import warning from 'tiny-warning'
65
+ // import warning from 'tiny-warning'
66
66
 
67
67
  //
68
68
 
@@ -125,10 +125,11 @@ export interface RouterOptions<
125
125
  routeTree?: TRouteTree
126
126
  basepath?: string
127
127
  context?: TRouteTree['types']['routerContext']
128
- // dehydrate?: () => TDehydrated
129
- // hydrate?: (dehydrated: TDehydrated) => void
128
+ dehydrate?: () => TDehydrated
129
+ hydrate?: (dehydrated: TDehydrated) => void
130
130
  routeMasks?: RouteMask<TRouteTree>[]
131
131
  unmaskOnReload?: boolean
132
+ Wrap?: (props: { children: any }) => JSX.Element
132
133
  }
133
134
 
134
135
  export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
@@ -249,7 +250,7 @@ export class Router<
249
250
  flatRoutes!: AnyRoute[]
250
251
 
251
252
  constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>) {
252
- this.updateOptions({
253
+ this.update({
253
254
  defaultPreloadDelay: 50,
254
255
  defaultPendingMs: 1000,
255
256
  defaultPendingMinMs: 500,
@@ -260,28 +261,17 @@ export class Router<
260
261
  })
261
262
  }
262
263
 
263
- startReactTransition: (fn: () => void) => void = () => {
264
- warning(
265
- false,
266
- 'startReactTransition implementation is missing. If you see this, please file an issue.',
267
- )
268
- }
269
-
270
- setState: (
271
- fn: (s: RouterState<TRouteTree>) => RouterState<TRouteTree>,
272
- ) => void = () => {
273
- warning(
274
- false,
275
- 'setState implementation is missing. If you see this, please file an issue.',
276
- )
264
+ // These are default implementations that can optionally be overridden
265
+ // by the router provider once rendered. We provide these so that the
266
+ // router can be used in a non-react environment if necessary
267
+ startReactTransition: (fn: () => void) => void = (fn) => fn()
268
+ setState: (updater: NonNullableUpdater<RouterState<TRouteTree>>) => void = (
269
+ updater,
270
+ ) => {
271
+ this.state = functionalUpdate(updater, this.state)
277
272
  }
278
273
 
279
- updateOptions = (
280
- newOptions: PickAsRequired<
281
- RouterOptions<TRouteTree, TDehydrated>,
282
- 'stringifySearch' | 'parseSearch' | 'context'
283
- >,
284
- ) => {
274
+ update = (newOptions: RouterConstructorOptions<TRouteTree, TDehydrated>) => {
285
275
  this.options = {
286
276
  ...this.options,
287
277
  ...newOptions,
@@ -293,7 +283,11 @@ export class Router<
293
283
  !this.history ||
294
284
  (this.options.history && this.options.history !== this.history)
295
285
  ) {
296
- this.history = this.options.history ?? createBrowserHistory()
286
+ this.history =
287
+ this.options.history ??
288
+ (typeof document !== 'undefined'
289
+ ? createBrowserHistory()
290
+ : createMemoryHistory())
297
291
  this.latestLocation = this.parseLocation()
298
292
  }
299
293
 
@@ -1320,6 +1314,7 @@ export class Router<
1320
1314
  // ...s,
1321
1315
  // status: 'idle',
1322
1316
  // resolvedLocation: s.location,
1317
+ // matches,
1323
1318
  // }))
1324
1319
 
1325
1320
  //
@@ -1582,62 +1577,69 @@ export class Router<
1582
1577
  return undefined
1583
1578
  }
1584
1579
 
1585
- // dehydrate = (): DehydratedRouter => {
1586
- // return {
1587
- // state: {
1588
- // dehydratedMatches: this.state.matches.map((d) =>
1589
- // pick(d, ['fetchedAt', 'invalid', 'id', 'status', 'updatedAt']),
1590
- // ),
1591
- // },
1592
- // }
1593
- // }
1580
+ dehydrate = (): DehydratedRouter => {
1581
+ return {
1582
+ state: {
1583
+ dehydratedMatches: this.state.matches.map((d) =>
1584
+ pick(d, [
1585
+ 'fetchedAt',
1586
+ 'invalid',
1587
+ 'id',
1588
+ 'status',
1589
+ 'updatedAt',
1590
+ 'loaderData',
1591
+ ]),
1592
+ ),
1593
+ },
1594
+ }
1595
+ }
1594
1596
 
1595
- // hydrate = async (__do_not_use_server_ctx?: HydrationCtx) => {
1596
- // let _ctx = __do_not_use_server_ctx
1597
- // // Client hydrates from window
1598
- // if (typeof document !== 'undefined') {
1599
- // _ctx = window.__TSR_DEHYDRATED__
1600
- // }
1601
-
1602
- // invariant(
1603
- // _ctx,
1604
- // 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?',
1605
- // )
1606
-
1607
- // const ctx = _ctx
1608
- // this.dehydratedData = ctx.payload as any
1609
- // this.options.hydrate?.(ctx.payload as any)
1610
- // const dehydratedState = ctx.router.state
1611
-
1612
- // let matches = this.matchRoutes(
1613
- // this.state.location.pathname,
1614
- // this.state.location.search,
1615
- // ).map((match) => {
1616
- // const dehydratedMatch = dehydratedState.dehydratedMatches.find(
1617
- // (d) => d.id === match.id,
1618
- // )
1619
-
1620
- // invariant(
1621
- // dehydratedMatch,
1622
- // `Could not find a client-side match for dehydrated match with id: ${match.id}!`,
1623
- // )
1624
-
1625
- // if (dehydratedMatch) {
1626
- // return {
1627
- // ...match,
1628
- // ...dehydratedMatch,
1629
- // }
1630
- // }
1631
- // return match
1632
- // })
1633
-
1634
- // this.setState((s) => {
1635
- // return {
1636
- // ...s,
1637
- // matches: dehydratedState.dehydratedMatches as any,
1638
- // }
1639
- // })
1640
- // }
1597
+ hydrate = async (__do_not_use_server_ctx?: HydrationCtx) => {
1598
+ let _ctx = __do_not_use_server_ctx
1599
+ // Client hydrates from window
1600
+ if (typeof document !== 'undefined') {
1601
+ _ctx = window.__TSR_DEHYDRATED__
1602
+ }
1603
+
1604
+ invariant(
1605
+ _ctx,
1606
+ 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?',
1607
+ )
1608
+
1609
+ const ctx = _ctx
1610
+ this.dehydratedData = ctx.payload as any
1611
+ this.options.hydrate?.(ctx.payload as any)
1612
+ const dehydratedState = ctx.router.state
1613
+
1614
+ let matches = this.matchRoutes(
1615
+ this.state.location.pathname,
1616
+ this.state.location.search,
1617
+ ).map((match) => {
1618
+ const dehydratedMatch = dehydratedState.dehydratedMatches.find(
1619
+ (d) => d.id === match.id,
1620
+ )
1621
+
1622
+ invariant(
1623
+ dehydratedMatch,
1624
+ `Could not find a client-side match for dehydrated match with id: ${match.id}!`,
1625
+ )
1626
+
1627
+ if (dehydratedMatch) {
1628
+ return {
1629
+ ...match,
1630
+ ...dehydratedMatch,
1631
+ }
1632
+ }
1633
+ return match
1634
+ })
1635
+
1636
+ this.setState((s) => {
1637
+ return {
1638
+ ...s,
1639
+ matches: matches as any,
1640
+ }
1641
+ })
1642
+ }
1641
1643
 
1642
1644
  // resolveMatchPromise = (matchId: string, key: string, value: any) => {
1643
1645
  // state.matches
@@ -1662,3 +1664,19 @@ export function lazyFn<
1662
1664
  function isCtrlEvent(e: MouseEvent) {
1663
1665
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
1664
1666
  }
1667
+ export class SearchParamError extends Error {}
1668
+
1669
+ export class PathParamError extends Error {}
1670
+
1671
+ export function getInitialRouterState(
1672
+ location: ParsedLocation,
1673
+ ): RouterState<any> {
1674
+ return {
1675
+ status: 'idle',
1676
+ resolvedLocation: location,
1677
+ location,
1678
+ matches: [],
1679
+ pendingMatches: [],
1680
+ lastUpdated: Date.now(),
1681
+ }
1682
+ }