null and undefined params are now automatically removed. Empty strings are stil kept because custom regexps can be an empty string
{ path: '/to-foo-query', redirect: '/foo?a=2#b' },
{ path: '/to-p/:p', redirect: { name: 'Param' } },
{ path: '/p/:p', name: 'Param', component: components.Bar },
+ { path: '/optional/:p?', name: 'optional', component: components.Bar },
{ path: '/repeat/:r+', name: 'repeat', component: components.Bar },
{ path: '/to-p/:p', redirect: to => `/p/${to.params.p}` },
{ path: '/redirect-with-param/:p', redirect: () => `/` },
expect(router.currentRoute.value).toMatchObject({ params: { p: '0' } })
})
+ it('casts null/undefined params to empty strings', async () => {
+ const { router } = await newRouter()
+ expect(
+ router.resolve({ name: 'optional', params: { p: undefined } })
+ ).toMatchObject({
+ params: {},
+ })
+ expect(
+ router.resolve({ name: 'optional', params: { p: null } })
+ ).toMatchObject({
+ params: {},
+ })
+ await router.push({ name: 'optional', params: { p: null } })
+ expect(router.currentRoute.value).toMatchObject({ params: {} })
+ await router.push({ name: 'optional', params: {} })
+ })
+
it('navigates to same route record but different query', async () => {
const { router } = await newRouter()
await router.push('/?q=1')
/**
* Encode characters that need to be encoded on the path section of the URL as a
* param. This function encodes everything {@link encodePath} does plus the
- * slash (`/`) character.
+ * slash (`/`) character. If `text` is `null` or `undefined`, returns an empty
+ * string instead.
*
* @param text - string to encode
* @returns encoded string
*/
-export function encodeParam(text: string | number): string {
- return encodePath(text).replace(SLASH_RE, '%2F')
+export function encodeParam(text: string | number | null | undefined): string {
+ return text == null ? '' : encodePath(text).replace(SLASH_RE, '%2F')
}
/**
NavigationGuardWithThis,
RouteLocationOptions,
MatcherLocationRaw,
+ RouteParams,
} from './types'
import { RouterHistory, HistoryState, NavigationType } from './history/common'
import {
paramValue => '' + paramValue
)
const encodeParams = applyToParams.bind(null, encodeParam)
- const decodeParams = applyToParams.bind(null, decode)
+ const decodeParams: (params: RouteParams | undefined) => RouteParams =
+ // @ts-expect-error: intentionally avoid the type check
+ applyToParams.bind(null, decode)
function addRoute(
parentOrRoute: RouteRecordName | RouteRecordRaw,
path: parseURL(parseQuery, rawLocation.path, currentLocation.path).path,
})
} else {
+ // remove any nullish param
+ const targetParams = assign({}, rawLocation.params)
+ for (const key in targetParams) {
+ if (targetParams[key] == null) {
+ delete targetParams[key]
+ }
+ }
// pass encoded values to the matcher so it can produce encoded path and fullPath
matcherLocation = assign({}, rawLocation, {
params: encodeParams(rawLocation.params),
/**
* @internal
*/
-export type RouteParamValueRaw = RouteParamValue | number
+export type RouteParamValueRaw = RouteParamValue | number | null | undefined
export type RouteParams = Record<string, RouteParamValue | RouteParamValue[]>
export type RouteParamsRaw = Record<
string,
- RouteParamValueRaw | RouteParamValueRaw[]
+ RouteParamValueRaw | Exclude<RouteParamValueRaw, null | undefined>[]
>
/**
export const assign = Object.assign
export function applyToParams(
- fn: (v: string | number) => string,
+ fn: (v: string | number | null | undefined) => string,
params: RouteParamsRaw | undefined
): RouteParams {
const newParams: RouteParams = {}