@tanstack/react-router 0.0.1-beta.227 → 0.0.1-beta.229

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.227",
4
+ "version": "0.0.1-beta.229",
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.227"
45
+ "@tanstack/history": "0.0.1-beta.229"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "rollup --config rollup.config.js"
package/src/Matches.tsx CHANGED
@@ -26,6 +26,7 @@ export interface RouteMatch<
26
26
  params: RouteById<TRouteTree, TRouteId>['types']['allParams']
27
27
  status: 'pending' | 'success' | 'error'
28
28
  isFetching: boolean
29
+ showPending: boolean
29
30
  invalid: boolean
30
31
  error: unknown
31
32
  paramsError: unknown
@@ -98,13 +99,22 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
98
99
  const PendingComponent = (route.options.pendingComponent ??
99
100
  options.defaultPendingComponent) as any
100
101
 
102
+ const pendingElement = PendingComponent
103
+ ? React.createElement(PendingComponent, {
104
+ useMatch: route.useMatch,
105
+ useRouteContext: route.useRouteContext,
106
+ useSearch: route.useSearch,
107
+ useParams: route.useParams,
108
+ })
109
+ : undefined
110
+
101
111
  const routeErrorComponent =
102
112
  route.options.errorComponent ??
103
113
  options.defaultErrorComponent ??
104
114
  ErrorComponent
105
115
 
106
116
  const ResolvedSuspenseBoundary =
107
- route.options.wrapInSuspense ?? PendingComponent
117
+ route.options.wrapInSuspense ?? pendingElement
108
118
  ? React.Suspense
109
119
  : SafeFragment
110
120
 
@@ -127,14 +137,7 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
127
137
 
128
138
  return (
129
139
  <matchesContext.Provider value={matches}>
130
- <ResolvedSuspenseBoundary
131
- fallback={React.createElement(PendingComponent, {
132
- useMatch: route.useMatch,
133
- useRouteContext: route.useRouteContext,
134
- useSearch: route.useSearch,
135
- useParams: route.useParams,
136
- })}
137
- >
140
+ <ResolvedSuspenseBoundary fallback={pendingElement}>
138
141
  <ResolvedCatchBoundary
139
142
  resetKey={locationKey}
140
143
  errorComponent={errorComponent}
@@ -142,25 +145,30 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
142
145
  warning(false, `Error in route match: ${match.id}`)
143
146
  }}
144
147
  >
145
- <MatchInner match={match} />
148
+ <MatchInner match={match} pendingElement={pendingElement} />
146
149
  </ResolvedCatchBoundary>
147
150
  </ResolvedSuspenseBoundary>
148
151
  </matchesContext.Provider>
149
152
  )
150
153
  }
151
- function MatchInner({ match }: { match: RouteMatch }): any {
154
+ function MatchInner({
155
+ match,
156
+ pendingElement,
157
+ }: {
158
+ match: RouteMatch
159
+ pendingElement: any
160
+ }): any {
152
161
  const { options, routesById } = useRouter()
153
162
  const route = routesById[match.routeId]!
154
163
 
155
- if (match.id.split('/').length === 4) {
156
- console.log(match.id, pick(match, ['status', 'cause', 'isFetching']))
157
- }
158
-
159
164
  if (match.status === 'error') {
160
165
  throw match.error
161
166
  }
162
167
 
163
168
  if (match.status === 'pending') {
169
+ if (match.showPending) {
170
+ return pendingElement || null
171
+ }
164
172
  throw match.loadPromise
165
173
  }
166
174
 
package/src/route.ts CHANGED
@@ -202,9 +202,7 @@ export type UpdatableRouteOptions<
202
202
  errorComponent?: ErrorRouteComponent<
203
203
  TFullSearchSchema,
204
204
  TAllParams,
205
- {}
206
- // TAllContext // TODO: I have no idea why this breaks the universe,
207
- // so we'll come back to it later.
205
+ TAllContext // NOTE: This used to break the universe.... but it seems to work now?
208
206
  > //
209
207
  // If supported by your framework, the content to be rendered as the fallback content until the route is ready to render
210
208
  pendingComponent?: PendingRouteComponent<
@@ -212,6 +210,8 @@ export type UpdatableRouteOptions<
212
210
  TAllParams,
213
211
  TAllContext
214
212
  >
213
+ pendingMs?: number
214
+ pendingMinMs?: number
215
215
  // Filter functions that can manipulate search params *before* they are passed to links and navigate
216
216
  // calls that match this route.
217
217
  preSearchFilters?: SearchFilter<TFullSearchSchema>[]
package/src/router.ts CHANGED
@@ -119,13 +119,11 @@ export interface RouterOptions<
119
119
  AnyPathParams,
120
120
  AnyContext
121
121
  >
122
- defaultMaxAge?: number
123
- defaultGcMaxAge?: number
124
- defaultPreloadMaxAge?: number
122
+ defaultPendingMs?: number
123
+ defaultPendingMinMs?: number
125
124
  caseSensitive?: boolean
126
125
  routeTree?: TRouteTree
127
126
  basepath?: string
128
- createRoute?: (opts: { route: AnyRoute; router: AnyRouter }) => void
129
127
  context?: TRouteTree['types']['routerContext']
130
128
  // dehydrate?: () => TDehydrated
131
129
  // hydrate?: (dehydrated: TDehydrated) => void
@@ -253,6 +251,8 @@ export class Router<
253
251
  constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>) {
254
252
  this.updateOptions({
255
253
  defaultPreloadDelay: 50,
254
+ defaultPendingMs: 1000,
255
+ defaultPendingMinMs: 500,
256
256
  context: undefined!,
257
257
  ...options,
258
258
  stringifySearch: options?.stringifySearch ?? defaultStringifySearch,
@@ -631,6 +631,7 @@ export class Router<
631
631
  search: {} as any,
632
632
  searchError: undefined,
633
633
  status: hasLoaders ? 'pending' : 'success',
634
+ showPending: false,
634
635
  isFetching: false,
635
636
  invalid: false,
636
637
  error: undefined,
@@ -1061,8 +1062,11 @@ export class Router<
1061
1062
  ...match,
1062
1063
  fetchedAt: Date.now(),
1063
1064
  invalid: false,
1065
+ showPending: false,
1064
1066
  }
1065
1067
 
1068
+ const pendingPromise = new Promise((r) => setTimeout(r, 1000))
1069
+
1066
1070
  if (match.isFetching) {
1067
1071
  loadPromise = getRouteMatch(this.state, match.id)?.loadPromise
1068
1072
  } else {
@@ -1146,45 +1150,83 @@ export class Router<
1146
1150
  }))
1147
1151
  }
1148
1152
 
1149
- try {
1150
- const loaderData = await loadPromise
1151
- if ((latestPromise = checkLatest())) return await latestPromise
1152
-
1153
- matches[index] = match = {
1154
- ...match,
1155
- error: undefined,
1156
- status: 'success',
1157
- isFetching: false,
1158
- updatedAt: Date.now(),
1159
- loaderData,
1160
- loadPromise: undefined,
1153
+ let didShowPending = false
1154
+
1155
+ await new Promise<void>(async (resolve) => {
1156
+ // If the route has a pending component and a pendingMs option,
1157
+ // forcefully show the pending component
1158
+ if (
1159
+ !preload &&
1160
+ (route.options.pendingComponent ??
1161
+ this.options.defaultPendingComponent) &&
1162
+ (route.options.pendingMs ?? this.options.defaultPendingMs)
1163
+ ) {
1164
+ pendingPromise.then(() => {
1165
+ didShowPending = true
1166
+ matches[index] = match = {
1167
+ ...match,
1168
+ showPending: true,
1169
+ }
1170
+
1171
+ this.setState((s) => ({
1172
+ ...s,
1173
+ matches: s.matches.map((d) =>
1174
+ d.id === match.id ? match : d,
1175
+ ),
1176
+ }))
1177
+ resolve()
1178
+ })
1161
1179
  }
1162
- } catch (error) {
1163
- if ((latestPromise = checkLatest())) return await latestPromise
1164
- if (handleIfRedirect(error)) return
1165
1180
 
1166
1181
  try {
1167
- route.options.onError?.(error)
1168
- } catch (onErrorError) {
1169
- error = onErrorError
1170
- if (handleIfRedirect(onErrorError)) return
1182
+ const loaderData = await loadPromise
1183
+ if ((latestPromise = checkLatest())) return await latestPromise
1184
+
1185
+ const pendingMinMs =
1186
+ route.options.pendingMinMs ?? this.options.defaultPendingMinMs
1187
+
1188
+ if (didShowPending && pendingMinMs) {
1189
+ await new Promise((r) => setTimeout(r, pendingMinMs))
1190
+ }
1191
+
1192
+ matches[index] = match = {
1193
+ ...match,
1194
+ error: undefined,
1195
+ status: 'success',
1196
+ isFetching: false,
1197
+ updatedAt: Date.now(),
1198
+ loaderData,
1199
+ loadPromise: undefined,
1200
+ }
1201
+ } catch (error) {
1202
+ if ((latestPromise = checkLatest())) return await latestPromise
1203
+ if (handleIfRedirect(error)) return
1204
+
1205
+ try {
1206
+ route.options.onError?.(error)
1207
+ } catch (onErrorError) {
1208
+ error = onErrorError
1209
+ if (handleIfRedirect(onErrorError)) return
1210
+ }
1211
+
1212
+ matches[index] = match = {
1213
+ ...match,
1214
+ error,
1215
+ status: 'error',
1216
+ isFetching: false,
1217
+ updatedAt: Date.now(),
1218
+ }
1171
1219
  }
1172
1220
 
1173
- matches[index] = match = {
1174
- ...match,
1175
- error,
1176
- status: 'error',
1177
- isFetching: false,
1178
- updatedAt: Date.now(),
1221
+ if (!preload) {
1222
+ this.setState((s) => ({
1223
+ ...s,
1224
+ matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1225
+ }))
1179
1226
  }
1180
- }
1181
1227
 
1182
- if (!preload) {
1183
- this.setState((s) => ({
1184
- ...s,
1185
- matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1186
- }))
1187
- }
1228
+ resolve()
1229
+ })
1188
1230
  })(),
1189
1231
  )
1190
1232
  })