Skip to content

Commit 04802fb

Browse files
authored
fix: make to required if from is not set (#3099)
* fix: make `to` required if `from` is not set * chore: fix build
1 parent 5c8c38a commit 04802fb

File tree

12 files changed

+117
-136
lines changed

12 files changed

+117
-136
lines changed

e2e/react-router/basic-file-based/src/routes/anchor.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ function AnchorComponent() {
8383
{anchors.map((anchor) => (
8484
<li key={anchor.id}>
8585
<Link
86+
from={Route.fullPath}
8687
data-testid={`link-${anchor.id}`}
8788
hash={anchor.id}
8889
activeOptions={{ includeHash: true }}

examples/react/basic-file-based/src/routes/anchor.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ function AnchorComponent() {
7979
{anchors.map((anchor) => (
8080
<li key={anchor.id}>
8181
<Link
82+
from={Route.fullPath}
8283
hash={anchor.id}
8384
activeOptions={{ includeHash: true }}
8485
activeProps={{

packages/react-router/src/Matches.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ import type {
1313
} from './structuralSharing'
1414
import type { AnyRoute, ReactNode, StaticDataRouteOption } from './route'
1515
import type { AnyRouter, RegisteredRouter, RouterState } from './router'
16-
import type { ResolveRelativePath, ResolveRoute, ToOptions } from './link'
16+
import type {
17+
MakeOptionalPathParams,
18+
MakeOptionalSearchParams,
19+
MaskOptions,
20+
ResolveRelativePath,
21+
ResolveRoute,
22+
ToSubOptionsProps,
23+
} from './link'
1724
import type {
1825
AllContext,
1926
AllLoaderData,
@@ -23,7 +30,6 @@ import type {
2330
RouteById,
2431
RouteByPath,
2532
RouteIds,
26-
RoutePaths,
2733
} from './routeInfo'
2834
import type {
2935
Constrain,
@@ -270,22 +276,15 @@ export interface MatchRouteOptions {
270276

271277
export type UseMatchRouteOptions<
272278
TRouter extends AnyRouter = RegisteredRouter,
273-
TFrom extends RoutePaths<TRouter['routeTree']> | string = RoutePaths<
274-
TRouter['routeTree']
275-
>,
276-
TTo extends string | undefined = '',
277-
TMaskFrom extends RoutePaths<TRouter['routeTree']> | string = TFrom,
279+
TFrom extends string = string,
280+
TTo extends string | undefined = undefined,
281+
TMaskFrom extends string = TFrom,
278282
TMaskTo extends string = '',
279-
TOptions extends ToOptions<
280-
TRouter,
281-
TFrom,
282-
TTo,
283-
TMaskFrom,
284-
TMaskTo
285-
> = ToOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
286-
TRelaxedOptions = Omit<TOptions, 'search' | 'params'> &
287-
DeepPartial<Pick<TOptions, 'search' | 'params'>>,
288-
> = TRelaxedOptions & MatchRouteOptions
283+
> = ToSubOptionsProps<TRouter, TFrom, TTo> &
284+
DeepPartial<MakeOptionalSearchParams<TRouter, TFrom, TTo>> &
285+
DeepPartial<MakeOptionalPathParams<TRouter, TFrom, TTo>> &
286+
MaskOptions<TRouter, TMaskFrom, TMaskTo> &
287+
MatchRouteOptions
289288

290289
export function useMatchRoute<TRouter extends AnyRouter = RegisteredRouter>() {
291290
const router = useRouter()

packages/react-router/src/index.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ export type {
4747
ParsePathParams,
4848
RemoveTrailingSlashes,
4949
RemoveLeadingSlashes,
50-
SearchPaths,
51-
SearchRelativePathAutoComplete,
52-
RelativeToParentPathAutoComplete,
53-
RelativeToCurrentPathAutoComplete,
54-
AbsolutePathAutoComplete,
50+
InferDescendantToPaths,
51+
RelativeToPath,
52+
RelativeToParentPath,
53+
RelativeToCurrentPath,
54+
AbsoluteToPath,
5555
RelativeToPathAutoComplete,
5656
NavigateOptions,
5757
ToOptions,

packages/react-router/src/link.tsx

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import type {
2727
RoutePaths,
2828
RouteToPath,
2929
ToPath,
30-
TrailingSlashOptionByRouter,
3130
} from './routeInfo'
3231
import type {
3332
AnyRouter,
@@ -80,78 +79,73 @@ export type RemoveLeadingSlashes<T> = T extends `/${string}`
8079
: T
8180
: T
8281

83-
export type FindDescendantPaths<
82+
export type FindDescendantToPaths<
8483
TRouter extends AnyRouter,
8584
TPrefix extends string,
8685
> = `${TPrefix}/${string}` & RouteToPath<TRouter>
8786

88-
export type SearchPaths<
87+
export type InferDescendantToPaths<
8988
TRouter extends AnyRouter,
9089
TPrefix extends string,
91-
TPaths = FindDescendantPaths<TRouter, TPrefix>,
90+
TPaths = FindDescendantToPaths<TRouter, TPrefix>,
9291
> = TPaths extends `${TPrefix}/`
9392
? never
9493
: TPaths extends `${TPrefix}/${infer TRest}`
9594
? TRest
9695
: never
9796

98-
export type SearchRelativePathAutoComplete<
97+
export type RelativeToPath<
9998
TRouter extends AnyRouter,
10099
TTo extends string,
101-
TSearchPath extends string,
100+
TResolvedPath extends string,
102101
> =
103-
| (TSearchPath & RouteToPath<TRouter> extends never
102+
| (TResolvedPath & RouteToPath<TRouter> extends never
104103
? never
105-
: ToPath<TrailingSlashOptionByRouter<TRouter>, TTo>)
106-
| `${TTo}/${SearchPaths<TRouter, RemoveTrailingSlashes<TSearchPath>>}`
104+
: ToPath<TRouter, TTo>)
105+
| `${RemoveTrailingSlashes<TTo>}/${InferDescendantToPaths<TRouter, RemoveTrailingSlashes<TResolvedPath>>}`
107106

108-
export type RelativeToParentPathAutoComplete<
107+
export type RelativeToParentPath<
109108
TRouter extends AnyRouter,
110109
TFrom extends string,
111110
TTo extends string,
112111
TResolvedPath extends string = ResolveRelativePath<TFrom, TTo>,
113112
> =
114-
| SearchRelativePathAutoComplete<TRouter, TTo, TResolvedPath>
113+
| RelativeToPath<TRouter, TTo, TResolvedPath>
115114
| (TTo extends `${string}..` | `${string}../`
116115
? TResolvedPath extends '/' | ''
117116
? never
118-
: FindDescendantPaths<
117+
: FindDescendantToPaths<
119118
TRouter,
120119
RemoveTrailingSlashes<TResolvedPath>
121120
> extends never
122121
? never
123-
: `${TTo}/${ParentPath<TrailingSlashOptionByRouter<TRouter>>}`
122+
: `${RemoveTrailingSlashes<TTo>}/${ParentPath<TRouter>}`
124123
: never)
125124

126-
export type RelativeToCurrentPathAutoComplete<
125+
export type RelativeToCurrentPath<
127126
TRouter extends AnyRouter,
128127
TFrom extends string,
129128
TTo extends string,
130129
TResolvedPath extends string = ResolveRelativePath<TFrom, TTo>,
131-
> =
132-
| SearchRelativePathAutoComplete<TRouter, TTo, TResolvedPath>
133-
| CurrentPath<TrailingSlashOptionByRouter<TRouter>>
130+
> = RelativeToPath<TRouter, TTo, TResolvedPath> | CurrentPath<TRouter>
134131

135-
export type AbsolutePathAutoComplete<
136-
TRouter extends AnyRouter,
137-
TFrom extends string,
138-
> =
132+
export type AbsoluteToPath<TRouter extends AnyRouter, TFrom extends string> =
139133
| (string extends TFrom
140-
? CurrentPath<TrailingSlashOptionByRouter<TRouter>>
134+
? CurrentPath<TRouter>
141135
: TFrom extends `/`
142136
? never
143-
: CurrentPath<TrailingSlashOptionByRouter<TRouter>>)
137+
: CurrentPath<TRouter>)
144138
| (string extends TFrom
145-
? ParentPath<TrailingSlashOptionByRouter<TRouter>>
139+
? ParentPath<TRouter>
146140
: TFrom extends `/`
147141
? never
148-
: ParentPath<TrailingSlashOptionByRouter<TRouter>>)
142+
: ParentPath<TRouter>)
149143
| RouteToPath<TRouter>
150144
| (TFrom extends '/'
151145
? never
152146
: string extends TFrom
153147
? never
154-
: SearchPaths<TRouter, RemoveTrailingSlashes<TFrom>>)
148+
: InferDescendantToPaths<TRouter, RemoveTrailingSlashes<TFrom>>)
155149

156150
export type RelativeToPathAutoComplete<
157151
TRouter extends AnyRouter,
@@ -160,20 +154,12 @@ export type RelativeToPathAutoComplete<
160154
> = string extends TTo
161155
? string
162156
: string extends TFrom
163-
? AbsolutePathAutoComplete<TRouter, TFrom>
157+
? AbsoluteToPath<TRouter, TFrom>
164158
: TTo & `..${string}` extends never
165159
? TTo & `.${string}` extends never
166-
? AbsolutePathAutoComplete<TRouter, TFrom>
167-
: RelativeToCurrentPathAutoComplete<
168-
TRouter,
169-
TFrom,
170-
RemoveTrailingSlashes<TTo>
171-
>
172-
: RelativeToParentPathAutoComplete<
173-
TRouter,
174-
TFrom,
175-
RemoveTrailingSlashes<TTo>
176-
>
160+
? AbsoluteToPath<TRouter, TFrom>
161+
: RelativeToCurrentPath<TRouter, TFrom, TTo>
162+
: RelativeToParentPath<TRouter, TFrom, TTo>
177163

178164
export type NavigateOptions<
179165
TRouter extends AnyRouter = RegisteredRouter,
@@ -234,15 +220,41 @@ export type ToSubOptions<
234220
SearchParamOptions<TRouter, TFrom, TTo> &
235221
PathParamOptions<TRouter, TFrom, TTo>
236222

237-
export interface ToSubOptionsProps<
238-
in out TRouter extends AnyRouter = RegisteredRouter,
239-
in out TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
240-
in out TTo extends string | undefined = '.',
223+
export interface RequiredToOptions<
224+
in out TRouter extends AnyRouter,
225+
in out TFrom extends string,
226+
in out TTo extends string | undefined,
227+
> {
228+
to: ToPathOption<TRouter, TFrom, TTo> & {}
229+
}
230+
231+
export interface OptionalToOptions<
232+
in out TRouter extends AnyRouter,
233+
in out TFrom extends string,
234+
in out TTo extends string | undefined,
241235
> {
242236
to?: ToPathOption<TRouter, TFrom, TTo> & {}
237+
}
238+
239+
export type MakeToRequired<
240+
TRouter extends AnyRouter,
241+
TFrom extends string,
242+
TTo extends string | undefined,
243+
> = string extends TFrom
244+
? string extends TTo
245+
? OptionalToOptions<TRouter, TFrom, TTo>
246+
: TTo & CatchAllPaths<TRouter> extends never
247+
? RequiredToOptions<TRouter, TFrom, TTo>
248+
: OptionalToOptions<TRouter, TFrom, TTo>
249+
: OptionalToOptions<TRouter, TFrom, TTo>
250+
251+
export type ToSubOptionsProps<
252+
TRouter extends AnyRouter = RegisteredRouter,
253+
TFrom extends RoutePaths<TRouter['routeTree']> | string = string,
254+
TTo extends string | undefined = '.',
255+
> = MakeToRequired<TRouter, TFrom, TTo> & {
243256
hash?: true | Updater<string>
244257
state?: true | NonNullableUpdater<HistoryState>
245-
// The source route path. This is automatically set when using route-level APIs, but for type-safe relative routing on the router itself, this is required
246258
from?: FromPathOption<TRouter, TFrom> & {}
247259
}
248260

@@ -319,7 +331,7 @@ export type ResolveToParams<
319331
? never
320332
: string extends TPath
321333
? ResolveAllToParams<TRouter, TParamVariant>
322-
: TPath extends CatchAllPaths<TrailingSlashOptionByRouter<TRouter>>
334+
: TPath extends CatchAllPaths<TRouter>
323335
? ResolveAllToParams<TRouter, TParamVariant>
324336
: ResolveRoute<
325337
TRouter,
@@ -404,7 +416,7 @@ export type IsRequired<
404416
ResolveRelativePath<TFrom, TTo> extends infer TPath
405417
? undefined extends TPath
406418
? never
407-
: TPath extends CatchAllPaths<TrailingSlashOptionByRouter<TRouter>>
419+
: TPath extends CatchAllPaths<TRouter>
408420
? never
409421
: IsRequiredParams<
410422
ResolveRelativeToParams<TRouter, TParamVariant, TFrom, TTo>
@@ -1014,7 +1026,11 @@ export function createLink<const TComp>(
10141026
export const Link: LinkComponent<'a'> = React.forwardRef<Element, any>(
10151027
(props, ref) => {
10161028
const { _asChild, ...rest } = props
1017-
const { type: _type, ref: innerRef, ...linkProps } = useLinkProps(rest, ref)
1029+
const {
1030+
type: _type,
1031+
ref: innerRef,
1032+
...linkProps
1033+
} = useLinkProps(rest as any, ref)
10181034

10191035
const children =
10201036
typeof rest.children === 'function'

packages/react-router/src/routeInfo.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,25 +59,30 @@ export type RouteIds<TRouteTree extends AnyRoute> =
5959
? CodeRouteIds<TRouteTree>
6060
: InferFileRouteTypes<TRouteTree>['id']
6161

62-
export type ParentPath<TOption> = 'always' extends TOption
63-
? '../'
64-
: 'never' extends TOption
65-
? '..'
66-
: '../' | '..'
67-
68-
export type CurrentPath<TOption> = 'always' extends TOption
69-
? './'
70-
: 'never' extends TOption
71-
? '.'
72-
: './' | '.'
73-
74-
export type ToPath<TOption, TTo extends string> = 'always' extends TOption
75-
? `${TTo}/`
76-
: 'never' extends TOption
77-
? TTo
78-
: TTo | `${TTo}/`
79-
80-
export type CatchAllPaths<TOption> = CurrentPath<TOption> | ParentPath<TOption>
62+
export type ParentPath<TRouter extends AnyRouter> =
63+
TrailingSlashOptionByRouter<TRouter> extends 'always'
64+
? '../'
65+
: TrailingSlashOptionByRouter<TRouter> extends 'never'
66+
? '..'
67+
: '../' | '..'
68+
69+
export type CurrentPath<TRouter extends AnyRouter> =
70+
TrailingSlashOptionByRouter<TRouter> extends 'always'
71+
? './'
72+
: TrailingSlashOptionByRouter<TRouter> extends 'never'
73+
? '.'
74+
: './' | '.'
75+
76+
export type ToPath<TRouter extends AnyRouter, TTo extends string> =
77+
TrailingSlashOptionByRouter<TRouter> extends 'always'
78+
? AddTrailingSlash<TTo>
79+
: TrailingSlashOptionByRouter<TRouter> extends 'never'
80+
? RemoveTrailingSlashes<TTo>
81+
: AddTrailingSlash<TTo> | RemoveTrailingSlashes<TTo>
82+
83+
export type CatchAllPaths<TRouter extends AnyRouter> =
84+
| CurrentPath<TRouter>
85+
| ParentPath<TRouter>
8186

8287
export type CodeRoutesByPath<TRouteTree extends AnyRoute> =
8388
ParseRoute<TRouteTree> extends infer TRoutes extends AnyRoute

packages/react-router/tests/Matches.test-d.tsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -155,26 +155,14 @@ test('when matching a route with params', () => {
155155
.parameter(0)
156156
.toHaveProperty('to')
157157
.toEqualTypeOf<
158-
| '/'
159-
| '.'
160-
| '..'
161-
| '/invoices'
162-
| '/invoices/$invoiceId'
163-
| '/comments/$id'
164-
| undefined
158+
'/' | '.' | '..' | '/invoices' | '/invoices/$invoiceId' | '/comments/$id'
165159
>()
166160

167-
expectTypeOf(MatchRoute<DefaultRouter, any, '/invoices/$invoiceId'>)
161+
expectTypeOf(MatchRoute<DefaultRouter, string, '/invoices/$invoiceId'>)
168162
.parameter(0)
169163
.toHaveProperty('to')
170164
.toEqualTypeOf<
171-
| '/'
172-
| '.'
173-
| '..'
174-
| '/invoices'
175-
| '/invoices/$invoiceId'
176-
| '/comments/$id'
177-
| undefined
165+
'/' | '.' | '..' | '/invoices' | '/invoices/$invoiceId' | '/comments/$id'
178166
>()
179167

180168
expectTypeOf(

0 commit comments

Comments
 (0)