Skip to content

Commit

Permalink
fix: do not use intersection for search and path params (#867)
Browse files Browse the repository at this point in the history
* fix: do not use intersection for search params

* fix: treat path params the same as search params w.r.t. intersection / union handling
  • Loading branch information
schiller-manuel authored Jan 2, 2024
1 parent d0bc0e5 commit eb1f989
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 115 deletions.
8 changes: 4 additions & 4 deletions packages/react-router/src/RouterProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ export interface MatchLocation {
}

export type NavigateFn<TRouteTree extends AnyRoute> = <
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
>(
opts: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
) => Promise<void>
Expand Down
156 changes: 67 additions & 89 deletions packages/react-router/src/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
PickRequired,
UnionToIntersection,
Updater,
WithoutEmpty,
deepEqual,
functionalUpdate,
} from './utils'
Expand Down Expand Up @@ -116,10 +117,10 @@ export type RelativeToPathAutoComplete<

export type NavigateOptions<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
> = ToOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
// `replace` is a boolean that determines whether the navigation should replace the current history entry or push a new one.
replace?: boolean
Expand All @@ -130,26 +131,26 @@ export type NavigateOptions<

export type ToOptions<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
> = ToSubOptions<TRouteTree, TFrom, TTo> & {
mask?: ToMaskOptions<TRouteTree, TMaskFrom, TMaskTo>
}

export type ToMaskOptions<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> | string = string,
TMaskTo extends string | undefined = undefined,
> = ToSubOptions<TRouteTree, TMaskFrom, TMaskTo> & {
unmaskOnReload?: boolean
}

export type ToSubOptions<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
> = {
to?: ToPathOption<TRouteTree, TFrom, TTo>
Expand All @@ -162,72 +163,56 @@ export type ToSubOptions<
// // When using relative route paths, this option forces resolution from the current path, instead of the route API's path or `from` path
} & CheckPath<TRouteTree, NoInfer<TResolved>, {}> &
SearchParamOptions<TRouteTree, TFrom, TTo, TResolved> &
PathParamOptions<TRouteTree, TFrom, TResolved>
PathParamOptions<TRouteTree, TFrom, TTo, TResolved>

export type SearchParamOptions<
type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)

export type ParamOptions<
TRouteTree extends AnyRoute,
TFrom,
TTo,
TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>,
TFromSearchEnsured = '/' extends TFrom
? FullSearchSchema<TRouteTree>
: Expand<
PickRequired<
RouteByPath<TRouteTree, TFrom>['types']['fullSearchSchema']
>
>,
TFromSearchOptional = Omit<
FullSearchSchema<TRouteTree>,
keyof TFromSearchEnsured
>,
TFromSearch = Expand<TFromSearchEnsured & TFromSearchOptional>,
TToSearch = '' extends TTo
? FullSearchSchema<TRouteTree>
: Expand<RouteByPath<TRouteTree, TResolved>['types']['fullSearchSchema']>,
> = keyof PickRequired<TToSearch> extends never
? {
search?: true | SearchReducer<TFromSearch, TToSearch>
}
: {
search: TFromSearchEnsured extends PickRequired<TToSearch>
? true | SearchReducer<TFromSearch, TToSearch>
: SearchReducer<TFromSearch, TToSearch>
}
TResolved,
TParamVariant extends 'allParams' | 'fullSearchSchema',
TFromParams = Expand<RouteByPath<TRouteTree, TFrom>['types'][TParamVariant]>,
TToParams = TTo extends undefined
? TFromParams
: never extends TResolved
? Expand<RouteByPath<TRouteTree, TTo>['types'][TParamVariant]>
: Expand<RouteByPath<TRouteTree, TResolved>['types'][TParamVariant]>,
TReducer = ParamsReducer<TFromParams, TToParams>,
> = Expand<WithoutEmpty<PickRequired<TToParams>>> extends never
? Partial<MakeParamOption<TParamVariant, true | TReducer>>
: TFromParams extends Expand<WithoutEmpty<PickRequired<TToParams>>>
? MakeParamOption<TParamVariant, true | TReducer>
: MakeParamOption<TParamVariant, TReducer>

type MakeParamOption<
TParamVariant extends 'allParams' | 'fullSearchSchema',
T,
> = TParamVariant extends 'allParams'
? MakePathParamOptions<T>
: MakeSearchParamOptions<T>
type MakeSearchParamOptions<T> = { search: T }
type MakePathParamOptions<T> = { params: T }

type SearchReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
export type SearchParamOptions<
TRouteTree extends AnyRoute,
TFrom,
TTo,
TResolved,
> = ParamOptions<TRouteTree, TFrom, TTo, TResolved, 'fullSearchSchema'>

export type PathParamOptions<
TRouteTree extends AnyRoute,
TFrom,
TTo,
TFromParamsEnsured = Expand<
UnionToIntersection<
PickRequired<RouteByPath<TRouteTree, TFrom>['types']['allParams']>
>
>,
TFromParamsOptional = Omit<AllParams<TRouteTree>, keyof TFromParamsEnsured>,
TFromParams = Expand<TFromParamsOptional & TFromParamsEnsured>,
TToParams = Expand<RouteByPath<TRouteTree, TTo>['types']['allParams']>,
> = never extends TToParams
? {
params?: true | ParamsReducer<Partial<TFromParams>, Partial<TFromParams>>
}
: keyof PickRequired<TToParams> extends never
? {
params?: true | ParamsReducer<TFromParams, TToParams>
}
: {
params: TFromParamsEnsured extends PickRequired<TToParams>
? true | ParamsReducer<TFromParams, TToParams>
: ParamsReducer<TFromParams, TToParams>
}

type ParamsReducer<TFrom, TTo> = TTo | ((current: TFrom) => TTo)
TResolved,
> = ParamOptions<TRouteTree, TFrom, TTo, TResolved, 'allParams'>

export type ToPathOption<
TRouteTree extends AnyRoute = AnyRoute,
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
> =
| TTo
| RelativeToPathAutoComplete<
Expand All @@ -238,8 +223,8 @@ export type ToPathOption<

export type ToIdOption<
TRouteTree extends AnyRoute = AnyRoute,
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | undefined = undefined,
TTo extends string | undefined = undefined,
> =
| TTo
| RelativeToPathAutoComplete<
Expand All @@ -256,10 +241,10 @@ export interface ActiveOptions {

export type LinkOptions<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
> = NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
// The standard anchor tag target attribute
target?: HTMLAnchorElement['target']
Expand Down Expand Up @@ -348,20 +333,13 @@ const preloadWarning = 'Error preloading route! ☝️'

export function useLinkProps<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
>({
from,
...options
}: UseLinkPropsOptions<
TRouteTree,
TFrom,
TTo,
TMaskFrom,
TMaskTo
>): React.AnchorHTMLAttributes<HTMLAnchorElement> {
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
>(
options: UseLinkPropsOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
): React.AnchorHTMLAttributes<HTMLAnchorElement> {
const router = useRouter()
const matchPathname = useMatch({
strict: false,
Expand Down Expand Up @@ -574,10 +552,10 @@ export function useLinkProps<
export interface LinkComponent<TProps extends Record<string, any> = {}> {
<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
>(
props: LinkProps<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
TProps &
Expand Down
43 changes: 21 additions & 22 deletions packages/react-router/src/useNavigate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { LinkOptions, NavigateOptions } from './link'
import { AnyRoute } from './route'
import { RoutePaths } from './routeInfo'
import { RegisteredRouter } from './router'
import { useLayoutEffect } from './utils'

export function useNavigate<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TDefaultFrom extends RoutePaths<TRouteTree> = '/',
TDefaultFrom extends RoutePaths<TRouteTree> | string = string,
>(_defaultOpts?: { from?: TDefaultFrom }) {
const { navigate, buildLocation } = useRouter()

Expand All @@ -20,10 +19,10 @@ export function useNavigate<

return React.useCallback(
<
TFrom extends RoutePaths<TRouteTree> = TDefaultFrom,
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = TDefaultFrom,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree>| string = TFrom,
TMaskTo extends string | undefined = undefined,
>({
from,
...rest
Expand Down Expand Up @@ -54,10 +53,10 @@ export function useNavigate<

export function Navigate<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
>(props: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>): null {
const { navigate } = useRouter()
const match = useMatch({ strict: false })
Expand All @@ -74,19 +73,19 @@ export function Navigate<

export type UseLinkPropsOptions<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined= undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
> = ActiveLinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
React.AnchorHTMLAttributes<HTMLAnchorElement>

export type LinkProps<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string| undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
> = ActiveLinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> &
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> & {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
Expand All @@ -97,10 +96,10 @@ export type LinkProps<

export type ActiveLinkOptions<
TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
TFrom extends RoutePaths<TRouteTree> = '/',
TTo extends string = '',
TMaskFrom extends RoutePaths<TRouteTree> = '/',
TMaskTo extends string = '',
TFrom extends RoutePaths<TRouteTree> | string = string,
TTo extends string | undefined = undefined,
TMaskFrom extends RoutePaths<TRouteTree> | string = TFrom,
TMaskTo extends string | undefined = undefined,
> = LinkOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo> & {
// A function that returns additional props for the `active` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)
activeProps?:
Expand Down
3 changes: 3 additions & 0 deletions packages/react-router/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export type PickRequired<T> = {
[K in keyof T as undefined extends T[K] ? never : K]: T[K]
}

// from https://stackoverflow.com/a/76458160
export type WithoutEmpty<T> = T extends T ? ({} extends T ? never : T) : never

// export type Expand<T> = T
export type Expand<T> = T extends object
? T extends infer O
Expand Down

0 comments on commit eb1f989

Please sign in to comment.