vue-router icon indicating copy to clipboard operation
vue-router copied to clipboard

Optional parameters are dropped in named nested routes

Open wrrrn opened this issue 6 years ago • 8 comments

Version

3.0.1

Reproduction link

~~https://codepen.io/wrrrn/pen/GxqmOb~~ http://jsfiddle.net/7a31maev/1/

Steps to reproduce

I've linked to what the current router code is. The issue is simple, so I hope I'm missing something obvious.

What is expected?

When the :paramOptional is set, and child route is pushed to, the path will look as such - '/search/paramOne/paramOptional/paramTwo/show-modal'

What is actually happening?

When navigated to, the child removes the paramOptional. The resulting url is - '/search/paramOne/paramTwo/show-modal'

wrrrn avatar Mar 15 '18 19:03 wrrrn

Adding a this.$router.push({name:'ShowModal', params: {paramOptional: this. paramOptionalData}}) fixes the issue but makes me sad.

wrrrn avatar Mar 15 '18 19:03 wrrrn

Edited to add an actual repro.

posva avatar Mar 16 '18 09:03 posva

Thanks for writing this one up. I was just about ready to do it myself but found yours :)

I'm wanting to add an optional parameter at the start of my app (/admin?) to control some admin functionality. For now I'm just using your workaround on adding this in the params.

Let me know if I can help at all

CalebKester avatar Sep 10 '18 13:09 CalebKester

you can work on a fix for this :)

posva avatar Sep 10 '18 14:09 posva

I've created a PR for this :) https://github.com/vuejs/vue-router/pull/2538

ElkeCodes avatar Dec 19 '18 14:12 ElkeCodes

@ElkeCodes Does this PR also work for this use case? /search/:a/(test/:b)?/:c http://jsfiddle.net/wf517tdu/1/

Tofandel avatar Jul 30 '20 17:07 Tofandel

Does this explain why in the following definition the meterId parameter is dropped when typing the route in the browser?

{
  name: "overview/meters",
  path: "/overview/:id/meters/:type/:meterId?",
  component: ListsPage,
  meta: {
    requiresAuth: true,
    breadcrumbs: true,
    access: ["site", "group"],
  },
  props: (route) => ({
    viewSource: "meters",
    options: {
      energyType: route.params.type,
      expandedMeterKeys: route.params.meterId ? [route.params.meterId] : [],
    },
  }),
},

So what's the status on this one? It is quite annoying to have to define two (almost identical) routes just to be able to use optional parameters.

macco3k avatar Jan 24 '22 16:01 macco3k

If someone is still looking for a better solution than having to define the optional param in every route object, I have found a solution although it is pretty hacky...

So I had the need to define an optional :lang parameter for my paths like this: path: '/:lang(en|bg|de)?/whatever/page' and this bug made the redirects go to /whatever/page even if the current route had the optional ':lang' part in its url.

So the first thing I had to do is force all route navigations to include the ':lang' as a param of the navigation if it was part of the current route:

// Global hook for fixing navigation with optional params removing the params from the path in some cases: https://github.com/vuejs/vue-router/issues/2110
// Needed for supporting our optional :lang parameter in routes
router.beforeEach((to, from, next) => {
    // Append the :lang param to the 'to' route if it exists on the 'from' route.
    if (from.params && from.params.lang) {
        if (to.params)
            to.params.lang = from.params.lang;
        else
            to.params = { lang: from.params.lang };
    }
    // Force navigation with a defined :lang param if the currently resolved path doesn't include it
    if (to.params && to.params.lang
        && to.fullPath != `/${to.params.lang}`
        && !to.fullPath.startsWith(`/${to.params.lang}/`)) {
        next({
            name: to.name,
            hash: to.hash,
            params: to.params,
            query: to.query,
        });
    } else {
        next();
    }
});

So far so good, however after that, for all <router-link> elements the active-class stopped working. Which happened because the route defined in the <router-link> elements didn't have the optional :lang param and the route was resolved without the :lang part and thus technically it was a different route. So I had to override the VueRouter 'resolve' method (which is used internally by <router-link> to check if the route is active) like so:

// Create Custom Router, to circumvent a vue-router bug with optional path parameters: https://github.com/vuejs/vue-router/issues/2110
// Needed for supporting our optional :lang parameter in routes
class CustomVueRouter extends VueRouter {
    // Override resolve method. Fixes router-link active-class not working when the :lang param is not explicitly defined.
    resolve(to, current, append) {
        // Append the :lang param to the 'to' route if it exists on the current.
        if (current && current.params && current.params.lang) {
            if (to.params)
                to.params.lang = current.params.lang;
            else
                to.params = { lang: current.params.lang };
        }
        return super.resolve(to, current, append);
    }
    // Override replace method. Fixes replace usage when the :lang param is not provided, but the current route has it defined.
    replace(location) {
        // Append the :lang param to the 'location' for replacing if it exists on the current route.
        if (this.history && this.history.current && this.history.current.params && this.history.current.params.lang) {
            if (location.params)
                location.params.lang = this.history.current.params.lang;
            else
                location.params = { lang: this.history.current.params.lang };
        }
        return super.replace(location);
    }
}

Vue.use(CustomVueRouter);
const router = new CustomVueRouter({
    routes,
    mode: 'history'
});

It again adds the :lang param to the route for resolution if it is present on the current route. Also as seen in the code snippet, I had to override the replace method, because it wasn't working correctly when :lang param was not provided and the current route had it defined.

Hopefully this helps someone fix their usage of optional params to behave more appropriately :)

abdonkov avatar Jul 07 '22 14:07 abdonkov