diff --git a/src/RouterLink.ts b/src/RouterLink.ts index 5c788ba36..505a219bd 100644 --- a/src/RouterLink.ts +++ b/src/RouterLink.ts @@ -302,8 +302,8 @@ function includesParams( } else { if ( !Array.isArray(outerValue) || - outerValue.length !== innerValue.length || - innerValue.some((value, i) => value !== outerValue[i]) + outerValue.length !== innerValue?.length || + innerValue?.some((value, i) => value !== outerValue[i]) ) return false } diff --git a/src/location.ts b/src/location.ts index 70cfbdc84..ec93f4660 100644 --- a/src/location.ts +++ b/src/location.ts @@ -158,7 +158,14 @@ export function isSameRouteLocationParams( if (Object.keys(a).length !== Object.keys(b).length) return false for (const key in a) { - if (!isSameRouteLocationParamsValue(a[key], b[key])) return false + const aValue = a[key], + bValue = b[key] + if ( + aValue === undefined || + bValue === undefined || + !isSameRouteLocationParamsValue(aValue, bValue) + ) + return false } return true diff --git a/src/matcher/index.ts b/src/matcher/index.ts index 3f1aea8d5..00f969f67 100644 --- a/src/matcher/index.ts +++ b/src/matcher/index.ts @@ -6,6 +6,7 @@ import { RouteRecordName, _RouteRecordProps, RouteRecordRedirect, + RouteParams, } from '../types' import { createRouterError, ErrorTypes, MatcherError } from '../errors' import { createRouteRecordMatcher, RouteRecordMatcher } from './pathMatcher' @@ -231,6 +232,19 @@ export function createRouterMatcher( location: Readonly, currentLocation: Readonly ): MatcherLocation { + // cannot use Required + type RouteParamsRequired = { + [P in keyof RouteParams]: NonNullable + } + const removeUndefinedFromRouteParams = ( + obj: RouteParams + ): RouteParamsRequired => { + for (const key in obj) { + if (obj[key] === undefined) delete obj[key] + } + return obj as RouteParamsRequired + } + let matcher: RouteRecordMatcher | undefined let params: PathParams = {} let path: MatcherLocation['path'] @@ -245,15 +259,17 @@ export function createRouterMatcher( }) name = matcher.record.name - params = assign( - // paramsFromLocation is a new object - paramsFromLocation( - currentLocation.params, - // only keep params that exist in the resolved location - // TODO: only keep optional params coming from a parent record - matcher.keys.filter(k => !k.optional).map(k => k.name) - ), - location.params + params = removeUndefinedFromRouteParams( + assign( + // paramsFromLocation is a new object + paramsFromLocation( + currentLocation.params, + // only keep params that exist in the resolved location + // TODO: only keep optional params coming from a parent record + matcher.keys.filter(k => !k.optional).map(k => k.name) + ), + location.params + ) ) // throws if cannot be stringified path = matcher.stringify(params) @@ -291,7 +307,11 @@ export function createRouterMatcher( name = matcher.record.name // since we are navigating to the same location, we don't need to pick the // params like when `name` is provided - params = assign({}, currentLocation.params, location.params) + params = assign( + {}, + removeUndefinedFromRouteParams(currentLocation.params), + removeUndefinedFromRouteParams(location.params ?? {}) + ) path = matcher.stringify(params) } diff --git a/src/types/index.ts b/src/types/index.ts index 49c439915..5654c4729 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -32,7 +32,9 @@ export type RouteParamValue = string * @internal */ export type RouteParamValueRaw = RouteParamValue | number | null | undefined -export type RouteParams = Record +export type RouteParams = { + [P in string]?: RouteParamValue | RouteParamValue[] +} export type RouteParamsRaw = Record< string, RouteParamValueRaw | Exclude[] diff --git a/test-dts/routeParams.test-d.ts b/test-dts/routeParams.test-d.ts new file mode 100644 index 000000000..8f36a5f57 --- /dev/null +++ b/test-dts/routeParams.test-d.ts @@ -0,0 +1,7 @@ +import { RouteParams, RouteParamsRaw, expectType } from './index' + +const params: RouteParams | RouteParamsRaw = { name: 'value' } +// @ts-expect-error +expectType(params.nonExist) +// @ts-expect-error +expectType(params.name)