@tanstack/react-router 1.81.5 → 1.81.7

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 (52) hide show
  1. package/dist/cjs/fileRoute.cjs.map +1 -1
  2. package/dist/cjs/fileRoute.d.cts +3 -2
  3. package/dist/cjs/index.d.cts +3 -2
  4. package/dist/cjs/location.d.cts +2 -2
  5. package/dist/cjs/matchContext.cjs +4 -0
  6. package/dist/cjs/matchContext.cjs.map +1 -1
  7. package/dist/cjs/matchContext.d.cts +1 -0
  8. package/dist/cjs/route.cjs.map +1 -1
  9. package/dist/cjs/route.d.cts +14 -51
  10. package/dist/cjs/routeInfo.d.cts +6 -6
  11. package/dist/cjs/router.cjs +13 -6
  12. package/dist/cjs/router.cjs.map +1 -1
  13. package/dist/cjs/router.d.cts +6 -5
  14. package/dist/cjs/searchParams.cjs.map +1 -1
  15. package/dist/cjs/searchParams.d.cts +3 -3
  16. package/dist/cjs/useMatch.cjs +3 -1
  17. package/dist/cjs/useMatch.cjs.map +1 -1
  18. package/dist/cjs/utils.cjs.map +1 -1
  19. package/dist/cjs/utils.d.cts +17 -5
  20. package/dist/cjs/validators.d.cts +49 -0
  21. package/dist/esm/fileRoute.d.ts +3 -2
  22. package/dist/esm/fileRoute.js.map +1 -1
  23. package/dist/esm/index.d.ts +3 -2
  24. package/dist/esm/location.d.ts +2 -2
  25. package/dist/esm/matchContext.d.ts +1 -0
  26. package/dist/esm/matchContext.js +4 -0
  27. package/dist/esm/matchContext.js.map +1 -1
  28. package/dist/esm/route.d.ts +14 -51
  29. package/dist/esm/route.js.map +1 -1
  30. package/dist/esm/routeInfo.d.ts +6 -6
  31. package/dist/esm/router.d.ts +6 -5
  32. package/dist/esm/router.js +13 -6
  33. package/dist/esm/router.js.map +1 -1
  34. package/dist/esm/searchParams.d.ts +3 -3
  35. package/dist/esm/searchParams.js.map +1 -1
  36. package/dist/esm/useMatch.js +4 -2
  37. package/dist/esm/useMatch.js.map +1 -1
  38. package/dist/esm/utils.d.ts +17 -5
  39. package/dist/esm/utils.js.map +1 -1
  40. package/dist/esm/validators.d.ts +49 -0
  41. package/package.json +3 -3
  42. package/src/fileRoute.ts +2 -2
  43. package/src/index.tsx +18 -15
  44. package/src/location.ts +2 -2
  45. package/src/matchContext.tsx +5 -0
  46. package/src/route.ts +32 -79
  47. package/src/routeInfo.ts +6 -6
  48. package/src/router.ts +30 -20
  49. package/src/searchParams.ts +2 -2
  50. package/src/useMatch.tsx +4 -2
  51. package/src/utils.ts +38 -8
  52. package/src/validators.ts +106 -0
package/src/router.ts CHANGED
@@ -42,8 +42,6 @@ import type {
42
42
  AnyContext,
43
43
  AnyRoute,
44
44
  AnyRouteWithContext,
45
- AnySearchSchema,
46
- AnySearchValidator,
47
45
  BeforeLoadContextOptions,
48
46
  ErrorRouteComponent,
49
47
  LoaderFnContext,
@@ -84,6 +82,7 @@ import type { AnyRedirect, ResolvedRedirect } from './redirects'
84
82
  import type { NotFoundError } from './not-found'
85
83
  import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
86
84
  import type { RouterTransformer } from './transformer'
85
+ import type { AnySchema, AnyValidator } from './validators'
87
86
 
88
87
  declare global {
89
88
  interface Window {
@@ -491,7 +490,7 @@ export interface RouterErrorSerializer<TSerializedError> {
491
490
 
492
491
  export interface RouterState<
493
492
  TRouteTree extends AnyRoute = AnyRoute,
494
- TRouteMatch = MakeRouteMatch<TRouteTree>,
493
+ TRouteMatch = MakeRouteMatchUnion,
495
494
  > {
496
495
  status: 'pending' | 'idle'
497
496
  loadedAt: number
@@ -579,10 +578,7 @@ function routeNeedsPreload(route: AnyRoute) {
579
578
  return false
580
579
  }
581
580
 
582
- function validateSearch(
583
- validateSearch: AnySearchValidator,
584
- input: unknown,
585
- ): unknown {
581
+ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
586
582
  if (validateSearch == null) return {}
587
583
 
588
584
  if ('~standard' in validateSearch) {
@@ -1075,7 +1071,7 @@ export class Router<
1075
1071
  */
1076
1072
  public matchRoutes(
1077
1073
  pathname: string,
1078
- locationSearch: AnySearchSchema,
1074
+ locationSearch: AnySchema,
1079
1075
  opts?: MatchRoutesOpts,
1080
1076
  ): Array<AnyRouteMatch>
1081
1077
  public matchRoutes(
@@ -1085,7 +1081,7 @@ export class Router<
1085
1081
 
1086
1082
  public matchRoutes(
1087
1083
  pathnameOrNext: string | ParsedLocation,
1088
- locationSearchOrOpts?: AnySearchSchema | MatchRoutesOpts,
1084
+ locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
1089
1085
  opts?: MatchRoutesOpts,
1090
1086
  ) {
1091
1087
  if (typeof pathnameOrNext === 'string') {
@@ -1246,9 +1242,11 @@ export class Router<
1246
1242
  // pending matches that are still loading
1247
1243
  const existingMatch = this.getMatch(matchId)
1248
1244
 
1249
- const cause = this.state.matches.find((d) => d.id === matchId)
1250
- ? 'stay'
1251
- : 'enter'
1245
+ const previousMatch = this.state.matches.find(
1246
+ (d) => d.routeId === route.id,
1247
+ )
1248
+
1249
+ const cause = previousMatch ? 'stay' : 'enter'
1252
1250
 
1253
1251
  let match: AnyRouteMatch
1254
1252
 
@@ -1256,7 +1254,12 @@ export class Router<
1256
1254
  match = {
1257
1255
  ...existingMatch,
1258
1256
  cause,
1259
- params: routeParams,
1257
+ params: previousMatch
1258
+ ? replaceEqualDeep(previousMatch.params, routeParams)
1259
+ : routeParams,
1260
+ search: previousMatch
1261
+ ? replaceEqualDeep(previousMatch.search, preMatchSearch)
1262
+ : replaceEqualDeep(existingMatch.search, preMatchSearch),
1260
1263
  }
1261
1264
  } else {
1262
1265
  const status =
@@ -1271,10 +1274,14 @@ export class Router<
1271
1274
  id: matchId,
1272
1275
  index,
1273
1276
  routeId: route.id,
1274
- params: routeParams,
1277
+ params: previousMatch
1278
+ ? replaceEqualDeep(previousMatch.params, routeParams)
1279
+ : routeParams,
1275
1280
  pathname: joinPaths([this.basepath, interpolatedPath]),
1276
1281
  updatedAt: Date.now(),
1277
- search: {} as any,
1282
+ search: previousMatch
1283
+ ? replaceEqualDeep(previousMatch.search, preMatchSearch)
1284
+ : preMatchSearch,
1278
1285
  searchError: undefined,
1279
1286
  status,
1280
1287
  isFetching: false,
@@ -1286,7 +1293,9 @@ export class Router<
1286
1293
  abortController: new AbortController(),
1287
1294
  fetchCount: 0,
1288
1295
  cause,
1289
- loaderDeps,
1296
+ loaderDeps: previousMatch
1297
+ ? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps)
1298
+ : loaderDeps,
1290
1299
  invalid: false,
1291
1300
  preload: false,
1292
1301
  links: route.options.links?.(),
@@ -1318,10 +1327,7 @@ export class Router<
1318
1327
  match.globalNotFound = globalNotFoundRouteId === route.id
1319
1328
  }
1320
1329
 
1321
- // Regardless of whether we're reusing an existing match or creating
1322
- // a new one, we need to update the match's search params
1323
- match.search = replaceEqualDeep(match.search, preMatchSearch)
1324
- // And also update the searchError if there is one
1330
+ // update the searchError if there is one
1325
1331
  match.searchError = searchError
1326
1332
 
1327
1333
  const parentMatchId = parentMatch?.id
@@ -2560,6 +2566,10 @@ export class Router<
2560
2566
  // to be preloaded before we resolve the match
2561
2567
  await route._componentsPromise
2562
2568
  } catch (err) {
2569
+ updateMatch(matchId, (prev) => ({
2570
+ ...prev,
2571
+ loaderPromise: undefined,
2572
+ }))
2563
2573
  handleRedirectAndNotFound(this.getMatch(matchId)!, err)
2564
2574
  }
2565
2575
  }
@@ -1,5 +1,5 @@
1
1
  import { decode, encode } from './qss'
2
- import type { AnySearchSchema } from './route'
2
+ import type { AnySchema } from './validators'
3
3
 
4
4
  export const defaultParseSearch = parseSearchWith(JSON.parse)
5
5
  export const defaultStringifySearch = stringifySearchWith(
@@ -8,7 +8,7 @@ export const defaultStringifySearch = stringifySearchWith(
8
8
  )
9
9
 
10
10
  export function parseSearchWith(parser: (str: string) => any) {
11
- return (searchStr: string): AnySearchSchema => {
11
+ return (searchStr: string): AnySchema => {
12
12
  if (searchStr.substring(0, 1) === '?') {
13
13
  searchStr = searchStr.substring(1)
14
14
  }
package/src/useMatch.tsx CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react'
2
2
  import invariant from 'tiny-invariant'
3
3
  import { useRouterState } from './useRouterState'
4
- import { matchContext } from './matchContext'
4
+ import { dummyMatchContext, matchContext } from './matchContext'
5
5
  import type {
6
6
  StructuralSharingOption,
7
7
  ValidateSelected,
@@ -84,7 +84,9 @@ export function useMatch<
84
84
  TStructuralSharing
85
85
  >,
86
86
  ): ThrowOrOptional<UseMatchResult<TRouter, TFrom, TStrict, TSelected>, TThrow> {
87
- const nearestMatchId = React.useContext(matchContext)
87
+ const nearestMatchId = React.useContext(
88
+ opts.from ? dummyMatchContext : matchContext,
89
+ )
88
90
 
89
91
  const matchSelection = useRouterState({
90
92
  select: (state: any) => {
package/src/utils.ts CHANGED
@@ -72,12 +72,12 @@ export type NonNullableUpdater<TPrevious, TResult = TPrevious> =
72
72
  | TResult
73
73
  | ((prev: TPrevious) => TResult)
74
74
 
75
- export type MergeUnionObjects<TUnion> = TUnion extends MergeUnionPrimitive
75
+ export type ExtractObjects<TUnion> = TUnion extends MergeAllPrimitive
76
76
  ? never
77
77
  : TUnion
78
78
 
79
- export type MergeUnionObject<TUnion> =
80
- MergeUnionObjects<TUnion> extends infer TObj
79
+ export type PartialMergeAllObject<TUnion> =
80
+ ExtractObjects<TUnion> extends infer TObj
81
81
  ? {
82
82
  [TKey in TObj extends any ? keyof TObj : never]?: TObj extends any
83
83
  ? TKey extends keyof TObj
@@ -87,23 +87,53 @@ export type MergeUnionObject<TUnion> =
87
87
  }
88
88
  : never
89
89
 
90
- export type MergeUnionPrimitive =
90
+ export type MergeAllPrimitive =
91
91
  | ReadonlyArray<any>
92
92
  | number
93
93
  | string
94
94
  | bigint
95
95
  | boolean
96
96
  | symbol
97
+ | undefined
98
+ | null
97
99
 
98
- export type MergeUnionPrimitives<TUnion> = TUnion extends MergeUnionPrimitive
100
+ export type ExtractPrimitives<TUnion> = TUnion extends MergeAllPrimitive
99
101
  ? TUnion
100
102
  : TUnion extends object
101
103
  ? never
102
104
  : TUnion
103
105
 
104
- export type MergeUnion<TUnion> =
105
- | MergeUnionPrimitives<TUnion>
106
- | MergeUnionObject<TUnion>
106
+ export type PartialMergeAll<TUnion> =
107
+ | ExtractPrimitives<TUnion>
108
+ | PartialMergeAllObject<TUnion>
109
+
110
+ /**
111
+ * To be added to router types
112
+ */
113
+ export type UnionToIntersection<T> = (
114
+ T extends any ? (arg: T) => any : never
115
+ ) extends (arg: infer T) => any
116
+ ? T
117
+ : never
118
+
119
+ /**
120
+ * Merges everything in a union into one object.
121
+ * This mapped type is homomorphic which means it preserves stuff! :)
122
+ */
123
+ export type MergeAllObjects<
124
+ TUnion,
125
+ TIntersected = UnionToIntersection<ExtractObjects<TUnion>>,
126
+ > = [keyof TIntersected] extends [never]
127
+ ? never
128
+ : {
129
+ [TKey in keyof TIntersected]: TUnion extends any
130
+ ? TUnion[TKey & keyof TUnion]
131
+ : never
132
+ }
133
+
134
+ export type MergeAll<TUnion> =
135
+ | MergeAllObjects<TUnion>
136
+ | ExtractPrimitives<TUnion>
107
137
 
108
138
  export type Constrain<T, TConstraint, TDefault = TConstraint> =
109
139
  | (T extends TConstraint ? T : never)
@@ -0,0 +1,106 @@
1
+ import type { SearchSchemaInput } from './route'
2
+
3
+ export interface StandardSchemaValidatorProps<TInput, TOutput> {
4
+ readonly types?: StandardSchemaValidatorTypes<TInput, TOutput> | undefined
5
+ readonly validate: AnyStandardSchemaValidate
6
+ }
7
+
8
+ export interface StandardSchemaValidator<TInput, TOutput> {
9
+ readonly '~standard': StandardSchemaValidatorProps<TInput, TOutput>
10
+ }
11
+
12
+ export type AnyStandardSchemaValidator = StandardSchemaValidator<any, any>
13
+
14
+ export interface StandardSchemaValidatorTypes<TInput, TOutput> {
15
+ readonly input: TInput
16
+ readonly output: TOutput
17
+ }
18
+
19
+ export interface AnyStandardSchemaValidateSuccess {
20
+ readonly value: any
21
+ readonly issues?: undefined
22
+ }
23
+
24
+ export interface AnyStandardSchemaValidateFailure {
25
+ readonly issues: ReadonlyArray<AnyStandardSchemaValidateIssue>
26
+ }
27
+
28
+ export interface AnyStandardSchemaValidateIssue {
29
+ readonly message: string
30
+ }
31
+
32
+ export interface AnyStandardSchemaValidateInput {
33
+ readonly value: any
34
+ }
35
+
36
+ export type AnyStandardSchemaValidate = (
37
+ value: unknown,
38
+ ) =>
39
+ | (AnyStandardSchemaValidateSuccess | AnyStandardSchemaValidateFailure)
40
+ | Promise<AnyStandardSchemaValidateSuccess | AnyStandardSchemaValidateFailure>
41
+
42
+ export interface ValidatorObj<TInput, TOutput> {
43
+ parse: ValidatorFn<TInput, TOutput>
44
+ }
45
+
46
+ export type AnyValidatorObj = ValidatorObj<any, any>
47
+
48
+ export interface ValidatorAdapter<TInput, TOutput> {
49
+ types: {
50
+ input: TInput
51
+ output: TOutput
52
+ }
53
+ parse: (input: unknown) => TOutput
54
+ }
55
+
56
+ export type AnyValidatorAdapter = ValidatorAdapter<any, any>
57
+
58
+ export type AnyValidatorFn = ValidatorFn<any, any>
59
+
60
+ export type ValidatorFn<TInput, TOutput> = (input: TInput) => TOutput
61
+
62
+ export type Validator<TInput, TOutput> =
63
+ | ValidatorObj<TInput, TOutput>
64
+ | ValidatorFn<TInput, TOutput>
65
+ | ValidatorAdapter<TInput, TOutput>
66
+ | StandardSchemaValidator<TInput, TOutput>
67
+ | undefined
68
+
69
+ export type AnyValidator = Validator<any, any>
70
+
71
+ export type AnySchema = {}
72
+
73
+ export type DefaultValidator = Validator<Record<string, unknown>, AnySchema>
74
+
75
+ export type ResolveValidatorInputFn<TValidator> = TValidator extends (
76
+ input: infer TSchemaInput,
77
+ ) => any
78
+ ? TSchemaInput extends SearchSchemaInput
79
+ ? Omit<TSchemaInput, keyof SearchSchemaInput>
80
+ : ResolveValidatorOutputFn<TValidator>
81
+ : AnySchema
82
+
83
+ export type ResolveValidatorInput<TValidator> =
84
+ TValidator extends AnyStandardSchemaValidator
85
+ ? NonNullable<TValidator['~standard']['types']>['input']
86
+ : TValidator extends AnyValidatorAdapter
87
+ ? TValidator['types']['input']
88
+ : TValidator extends AnyValidatorObj
89
+ ? ResolveValidatorInputFn<TValidator['parse']>
90
+ : ResolveValidatorInputFn<TValidator>
91
+
92
+ export type ResolveValidatorOutputFn<TValidator> = TValidator extends (
93
+ ...args: any
94
+ ) => infer TSchema
95
+ ? TSchema
96
+ : AnySchema
97
+
98
+ export type ResolveValidatorOutput<TValidator> = unknown extends TValidator
99
+ ? TValidator
100
+ : TValidator extends AnyStandardSchemaValidator
101
+ ? NonNullable<TValidator['~standard']['types']>['output']
102
+ : TValidator extends AnyValidatorAdapter
103
+ ? TValidator['types']['output']
104
+ : TValidator extends AnyValidatorObj
105
+ ? ResolveValidatorOutputFn<TValidator['parse']>
106
+ : ResolveValidatorOutputFn<TValidator>