vuejs / router

🚦 The official router for Vue.js
https://router.vuejs.org/
MIT License
3.77k stars 1.16k forks source link

Vue router crashes when top level route target is wrapped in transition but has no child routes yet #2121

Closed robert-wloch-iits closed 4 months ago

robert-wloch-iits commented 6 months ago

Reproduction

For reproduction please see the provided Stackblitz project

Steps to reproduce the bug

Here's a stackblitz project reproducing the issue: Vue + Vue Router Error Reproduction

Howto see the reproduction

First four steps show that transitions are working for Route A and Route B, even when going to Route C.

  1. Navigate to "Works A"
  2. Navigate to "Works B"
  3. Navigate to "Works A"
  4. Navigate to "Crashes C"
  5. Navigate to "Works A" and see the the router views are not rendered anymore.

Expected behavior

Vue router should silently ignore when there are no children defined in the router config yet (iterative approach of software development). Even if it doesn't ignore them, navigation away to a previously working route should be possible without having to reload the application in the browser tab.

Actual behavior

The top level router view in App.vue is wrapped in a Transition and for all top level router targets view components are there. Three top level targets defined in their template a child <ViewRouter></ViewRouter> but the third, "Crashes C" has no children defined yet. When navigating to that top level target "Crashes C" and then to some other route, view router crashes.

Side-effect: When staying in Route "Crashes C" reloading the browser tab to restart the app will let a user stick in that route since navigating away from it will crash the vue router again.

Additional information

Edit on 2024-02-01: I've updated the StackBlitz to contain the Routes "Works D", "Crashes E" and "Works F" with the examples codes from my comments below. Specifically "Crashes E" and "Works F" are a real mystery since the only difference is the missing commented out RouterView in the template section in "Works F".

Edit on 2024-02-02: With @K332's answer below I gave the final section in the official documentation a try, which adds :key="route.path" to the inner component tag. Doing that, I now get to see the error in the browser console when navigating away from a "Crashes" route:

[Vue warn]: Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://github.com/vuejs/core . 
  at <BaseTransition mode="out-in" appear=true persisted=false  ... > 
  at <Transition name="fade" mode="out-in" appear="" > 
  at <RouterView class="content" > 
  at <App>
chunk-ESTIRR4N.js?v=f0cae026:9623 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'parentNode')
    at parentNode (chunk-ESTIRR4N.js?v=f0cae026:9623:30)
    at ReactiveEffect.componentUpdateFn [as fn] (chunk-ESTIRR4N.js?v=f0cae026:7531:11)
    at ReactiveEffect.run (chunk-ESTIRR4N.js?v=f0cae026:429:19)
    at instance.update (chunk-ESTIRR4N.js?v=f0cae026:7571:17)
    at leavingHooks.afterLeave (chunk-ESTIRR4N.js?v=f0cae026:3679:24)
    at performRemove (chunk-ESTIRR4N.js?v=f0cae026:8018:20)
    at remove2 (chunk-ESTIRR4N.js?v=f0cae026:8030:7)
    at unmount (chunk-ESTIRR4N.js?v=f0cae026:7985:9)
    at unmountComponent (chunk-ESTIRR4N.js?v=f0cae026:8053:7)
    at unmount (chunk-ESTIRR4N.js?v=f0cae026:7954:7)

No response

posva commented 6 months ago

It looks like a dup of one of the transition issues in Vue core. Maybe https://github.com/vuejs/core/issues/3950. Feel free to investigate further.

robert-wloch-iits commented 6 months ago

I've found that it is even caused by commented out RouterView code. When you replace the contents of RouteCView.vue in the StackBlitz project by the following:

<script setup lang="ts">
</script>

<template>
<div>Ranking View</div>
</template>

then navigation between all routes is working fine.

But when you use that code, even though it does exactly the same as above, router navigation is broken as well:

<script setup lang="ts">
//import { RouterView } from 'vue-router'
</script>

<template>
  <div>Ranking View</div>
  <!--<RouterView></RouterView>-->
</template>
robert-wloch-iits commented 6 months ago

It looks like a dup of one of the transition issues in Vue core. Maybe vuejs/core#3950. Feel free to investigate further.

I don't see how this is related. I'm not using keep-alive here in my example. With my previous comment one can suspect, that there's a deeper issue than vue-router, though.

yueyunkeji commented 5 months ago

vant4源代码中移动端路由如果是树形的貌似不被支持

robert-wloch-iits commented 5 months ago

vant4源代码中移动端路由如果是树形的貌似不被支持

According to deepl your comment translates to:

Mobile routing in the vant4 source code does not seem to be supported if it is tree-based

I don't understand the relation to my post. What do you mean by "Mobile routing"? I have a client-side SPA. The problem described happens in the local desktop browser, not on a mobile device.

robert-wloch-iits commented 5 months ago

Also, given my comment with the examples above, consider the last example vs this one:

<script setup lang="ts">
//import { RouterView } from 'vue-router'
</script>

<template>
  <div>Ranking View</div>
</template>

When you remove the commented out <!--<RouterView></RouterView>--> line in the template code, the component and router will behave correctly and work as expected.

My guess is, that the commented out code is not treated as commented out. That might be a totally separate issue with the vue template compiler, but I'm not sure about that. Any thoughts on that @posva?

K332 commented 5 months ago

well, there are two ways to solve it

1: image

2: image

robert-wloch-iits commented 5 months ago

According to the official documentation 2. doesn't resemble the examples.

✅ Confirmed that 1. solves the issue and is closest to the official documentation. But it needs to use $route as it cannot use the destructured slot prop route. That's not clean code. Why would one then destructure route from slot props to use it in the Transition as shown in the documentation? If that :key is required on the RouterView and it can only be used with $route then the documenation should not destructure route for the transition but use $route there as well. Or, probably better for Composition API, do a const route = useRoute() in the script section first.

import { RouterView, useRoute } from 'vue-router'
const route = useRoute()
...
<RouterView v-slot="{ Component }" class="content" :key="route.fullPath">
  <Transition :name="(route?.meta?.transition as string) || 'fade'" mode="out-in" appear>
    <component :is="Component" />
  </Transition>
</RouterView>

With 1. solving the issue, the official documentation is wrong and that issue here should be used to update that documentation please.

robert-wloch-iits commented 5 months ago

PR for updated documentation: https://github.com/vuejs/router/pull/2126

skirtles-code commented 4 months ago

I believe it's this: https://github.com/vuejs/core/issues/6080.

The <!--<RouterView></RouterView>--> comment in route E is slightly misleading. It would fail for any comment, it doesn't have to be that specific comment.

With route C you end up in effectively the same scenario, because <RouterView> renders a comment node if it can't render anything else.

posva commented 4 months ago

Indeed, wrapping the <router-view> from view C with a div or other HTML element and removing (instead of commenting) in view e makes everything work. Thanks a lot @skirtles-code !