Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug Report][3.1.14] Components built with @/composables/router can crash when routes are missing required params #17176

Open
MatthewAry opened this issue Apr 18, 2023 · 20 comments
Labels
framework Issues and Feature Requests that have needs framework-wide. help wanted We are looking for community help T: bug Functionality that does not work as intended/expected
Milestone

Comments

@MatthewAry
Copy link
Contributor

MatthewAry commented Apr 18, 2023

Environment

Vuetify Version: 3.1.14
Vue Version: 3.2.47
Browsers: Chrome 112.0.0.0
OS: Mac OS 10.15.7

Steps to reproduce

This is a really dirty reproduction. Clone, and npm i and run.
There are two buttons, click on "VBtn Flaw" and then click on the home button in the top right corner.

Expected Behavior

Click on "Router Link" and then click on the home button on the top right corner. And see the expected behavior.

I expect that when you use a named route which is a child to a parent(s) with param(s) that it will inherit the params implicitly from it's parent, you shouldn't need to declare the params that are missing.

Actual Behavior

VBtn in the example crashes when you navigate back to home.

Reproduction Link

https://github.com/MatthewAry/vuetify-route-link-bug

Other comments

This reproduction shows the behavior with RouterLink and with VBtn the to params on the elements shown on those pages are basically the same. I think behaviorally when it comes to routing, there should be no difference between RouterLink and VBtn or any other component that uses @/composables/router

Update: vuejs/router#845 has to do with this exact problem however the issue was closed because the team believed it was resolved. If you build your application in production mode you won't see this issue. See: vuejs/router#845 (comment) Which suggests that this issue may be upstream or that the useLink method in @/composables/router needs to change in some manner to be more similar to what the RouterLink component is doing to avoid this issue. 🤷

Update: See #17176 (comment)

@MatthewAry
Copy link
Contributor Author

May be related to vuejs/router#845

@MatthewAry MatthewAry changed the title [Bug Report][3.1.14] Components built with @/composables/router can crash when missing required params [Bug Report][3.1.14] Components built with @/composables/router can crash when routes are missing required params Apr 18, 2023
@MatthewAry
Copy link
Contributor Author

MatthewAry commented Apr 18, 2023

This issue doesn't seem to be caused by the code related to vuejs/router@4108814 as commenting out the current version of the dev tools code and running the application does not resolve this issue. Instead it seems that the buttons are getting re-rendered when the route changes causing it to re-resolve the route link which becomes impossible because at the time of re-rendering the routable buttons, the router has already changed its state to not have the relevant params.

@MatthewAry
Copy link
Contributor Author

MatthewAry commented Apr 21, 2023

So after quite a bit of investigation this is what I've learned. Components in Vuetify that make use of the useLink function from Vue Router's RouterLink component, utilize a few ComputedRef properties that useLink returns which are bound to the router's current state. The reactive properties used are:

  • route the current RouteLocation & { href: string }
  • isActive tells us if the specified route for the link is included in the list of matched routes for the current, "visiting" route
  • isExactActive tells us if the current route being visited is the exact same route as the one specified for the link
  • href the path to the route specified for the router link. This is extracted from the same route object detailed above.

Vuetify's useLink composable function utilizes the reactive properties listed above (with the exception of href, it gets that from route instead of using the one returned from Vue Router's own useLink function) to turn it's own components into proper links using <a> tags. This functionally similar in many ways to what Vue Router does when you use its RouterLink component. However, for some reason the behavior is different.

What I am seeing is that every time the current route changes, Vuetify's router enabled components get an update, causing it to re-resolve the route for every link. And when the new route is missing params required for the links' named route, the resolution of the href property fails causing the crash. You would expect components to unmount gracefully when you leave a route that loads and renders router link components using named routes which require params, what happens is that this route re-resolve operation takes place BEFORE the components get unmounted. This is basically a race condition.

I have come up with a workaround, but it should be considered a hack and not a solution. This hack works for onRouteEnter but not for when your params change onRouteUpdate, so you'll need to figure out how to handle onRouteUpdate because that will require more considerations depending on your use case.

In the parent component, you need to obtain the current router params and store them in a const.

const routeParams = { ...useRoute().params };

routeParams will end up being assigned the route parameters that were present the moment the parent component was rendered and will be a non-reactive object.

Then in your template you will want to do something like this.

<VBtn :to="{ name: 'RouteName', params: routeParams }">Your Router Link Button</VBtn>

This way you'll be passing in the necessary params to make your link properly resolve even if your route changes to something that doesn't contain the necessary params.

@villageseo
Copy link

Awesome work @MatthewAry ! I can confirm your workaround. I also pass params to v-tab (in my case).

@MatthewAry
Copy link
Contributor Author

I asked @posva about this and he said:

Once you have a boiled down reproduction only with vue router, open a new issue. What you are describing is unlikely caused by the router as that hasn't changed in a long while. we have seen however similar problems when using watchers on the routes and they have been reported in vue core in the past. Maybe that helps you reproduce it
https://discord.com/channels/325477692906536972/325479452773580800/1098990698549743710

@Oleksii14
Copy link

Confirming this issue on my side as well 🙏🏻

@MatthewAry
Copy link
Contributor Author

MatthewAry commented Apr 21, 2023

I have created hack that should satisfy most use-cases but I have not done comprehensive testing of it. My hope is that it will make sure the required params are fed to the routable components. Also, please note that this solution has room for optimization. If you moved the routes map out of the SafelyResolveRouterParams class into a global, and you properly update it when you perform removeRoute and addRoute operations, it should reduce memory and compute for the components that have their own class instance.

https://gist.github.com/MatthewAry/7129cfe405f93bb5827e7cbd7928dd40

Update: safeNamedLocation gets called a lot. For example, Just hovering over an element that is using this method can trigger three function calls.

@KaelWD KaelWD added T: bug Functionality that does not work as intended/expected framework Issues and Feature Requests that have needs framework-wide. labels Apr 26, 2023
@KaelWD KaelWD added this to the v3.2.x milestone Apr 26, 2023
@1Luc1
Copy link

1Luc1 commented Apr 28, 2023

Maybe I don't understand the actual issue or I don't know how to use vue-router "correctly", but isn't the problem, that the params: { pid: '...' } for v-btn :to within the views/example/Example.vue file aren't defined? Like it is in components/HelloWorld.vue.

@MatthewAry
Copy link
Contributor Author

MatthewAry commented Apr 29, 2023 via email

@1Luc1
Copy link

1Luc1 commented May 10, 2023

Playing around a bit, i got the same problem with <v-list> and props property on the items, as you already mentioned. Nevertheless, the error seems a little bit different. I post my findings here; maybe it can help to solve this problem.

I set up an quick example about the problem here: https://github.com/1Luc1/list-props-to-bug

Basically there are two Layouts. One Layout holds a child component. Switching from one layout to the other causes the following browser warning during development (npm run dev):

[Vue warn]: Unhandled error during execution of watcher callback 
  at <VListItem title="Right View #1" value=undefined prependIcon="$arrowRight"  ... > 
  at <VListChildren items= Array [ {…}, {…} ] > 
  at <VList items= Array [ {…}, {…} ] density="compact" active-color="primary" > 
  at <VCol cols="auto" > 
  at <VRow class="d-flex justify-center" > 
  at <VResponsive class="d-flex text-center fill-height" > 
  at <VContainer class="fill-height" > 
  at <BaseLayout onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< Proxy { <target>: Proxy, <handler>: {…} } > > 
  at <RouterView> 
  at <App> runtime-core.esm-bundler.js:40:16

I noticed, that for the list items, the missing params: {id: 1} which causes the failure seems to be inherited from the parent layout, since the path showing in the browser is correct.

Building and running the app, leads to the following error:

Error: Missing required param "id"
...

Vuetify Version: 3.2.3
Vue Version: 3.2.47
Vue Router: 4.1.6
Browsers: FireFox 113.0
OS: Manjaro

Update: After 4 months without any reasonable solution for this problem I'm using MatthewAry's workaround for now, which works quite well for me. I updated my issue repo with his workaround and hope this issues will get resolved in the future.

@rachitpant
Copy link

@MatthewAry Could you please explain how your hack/fix applies to the bug i reported earlier ( #17614 ), As described in this bug , we are unable to navigate to the /applications route from /applications/1/about ( or any other specific application page ) , and vuetify is complaining about missing params ( and your solution shows how we can pass the params ).

However i don't understand why we would need params to navigate to /applications. The missing route param id should only be needed when navigating to /applications/1/about or /applications/1 ( specific application ). I am successfully able to open a specific application and navigate to a tab. The missing param error happens when trying to move back to list of applications. ( /applications ).

Also once you try navigating to /applications and this error occurs, it completely breaks routing in the app. You can't even navigate to / home page.
Would appreciate if you could fork the sandox with your hack.

@rachitpant
Copy link

I was able to do this and your patch resolves the issue in my codebase also. Thanks.

@cadriel
Copy link
Contributor

cadriel commented Jun 27, 2023

vuejs/vue-test-utils#2069

Linking this here since it seems relevant.

@MatthewAry
Copy link
Contributor Author

@johnleider can we expect this to be fixed in blackguard?

@johnleider
Copy link
Member

@johnleider can we expect this to be fixed in blackguard?

We don't have someone specifically on it atm. Would you be interested in taking the reigns on this?

@MatthewAry
Copy link
Contributor Author

MatthewAry commented Aug 7, 2023

I might have the confidence to take this on in the future, but right now I don't because this bug is weird and the actual cause of the problem is unknown. It would be better to figure out the cause then to just make a workaround. With that said, I think a workaround solution is possible.

We can probably use RegEx, Matched Routes, and Resolve methods in the router to solve this; but extensive tests would need to be written to ensure that all functionality detailed https://router.vuejs.org/guide/essentials/route-matching-syntax.html is maintained.

This code might help but I'm not that great with RegEx so there might be bugs:

function resolvePath(path, params) {
    // Handle optional params
    path = path.replace(/\?/g, "\\?")

    // Handle repeating params
    path = path.replace(/\*/g, "(\\/[^/]+)*")
    path = path.replace(/\+/g, "(\\/[^/]+)+")

    // Handle custom regex
    path = path.replace(/:\w+\([^\)]*\)/g, "([^/]+)")

    // Extract all dynamic params
    const paramMatches = path.match(/:[^/()]+/g)

    if (!paramMatches) return path

    // Replace each param with its value
    let resolvedPath = path
    for (const param of paramMatches) {
      const name = param.slice(1)
      if (name in params) {
        resolvedPath = resolvedPath.replace(param, params[name])
      }
    }

    return resolvedPath
}

Using the router's resolve method could return a path for a named route but without any of the params applied. The function above can then take the params provided in an object and try to apply them to the path by matching against the path's keys. This solution however leaves the actual router out of a part of the route resolution process. Honestly it would be better if the router would allow us to provide an excessive amount of params and let it handle it instead of doing something like this.

@KaelWD KaelWD modified the milestones: v3.3.x, v3.4.x Nov 3, 2023
@johnleider johnleider modified the milestones: v3.4.x, v3.5.x Jan 10, 2024
@AlexianMasson
Copy link

AlexianMasson commented Jan 29, 2024

Hello,

we had the same issue and we resolved it by upgrading some dependencies (with yarn upgrade-interactive) :

  • vueuse/core, 10.5.0 ❯ 10.7.2
  • vue-router, 4.2.4 ❯ 4.2.5
  • vue-tsc, 1.8.4 ❯ 1.8.27
  • vuetify, 3.4.9 ❯ 3.4.11

@HYPE-Thomas
Copy link

Hello,

we had the same issue and we resolved it by upgrading some dependencies (with yarn upgrade-interactive) :

  • vueuse/core, 10.5.0 ❯ 10.7.2
  • vue-router, 4.2.4 ❯ 4.2.5
  • vue-tsc, 1.8.4 ❯ 1.8.27
  • vuetify, 3.4.9 ❯ 3.4.11

Sadly, for us this did not work out. We were already on the mentioned versions and still experience the bug. Hoping for a timely resolution of this issue.

@johnleider
Copy link
Member

We're looking for help on this issue.

@johnleider johnleider added the help wanted We are looking for community help label Jan 30, 2024
@johnleider johnleider modified the milestones: v3.5.x, v3.6.x Apr 30, 2024
@ayZagen

This comment was marked as duplicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
framework Issues and Feature Requests that have needs framework-wide. help wanted We are looking for community help T: bug Functionality that does not work as intended/expected
Projects
None yet
Development

No branches or pull requests