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

Nested routes from Vue Router break when used inside PortalVue

Open boardend opened this issue 5 years ago • 8 comments
trafficstars

Version

2.1.7

Reproduction link

https://codesandbox.io/s/great-elgamal-y285j

  • The problem can be toggled with the checkbox inside the footer.
  • See router.js, MainView.vue and ModalOutlet.vue.

Steps to reproduce

Use <router-view /> with nested routes inside a <portal> to render the nested routes somewhere else in the app (e.g. a lightbox modal that is logically a child of some other component/route, but should be rendered somewhere close to the root component).

What is expected?

The route matching will work the same, no matter if the <router-view /> is inside a <portal> or not.

What is actually happening?

When inside a <portal>, the nested route gets replaced with the parent route from where the <portal> was used.

boardend avatar Mar 04 '20 15:03 boardend

This would be because the child <router-view> is no longer nested within the parent <router-view> when portalled: the parent hierarchy changes (https://portal-vue.linusb.org/guide/caveats.html#known-caveats) if your <portal-target> is not rendered within your parent <router-view> container.

tmorehouse avatar Mar 04 '20 15:03 tmorehouse

One workaround would be to use named <router-view>s, and set the "child" route to be targeted to that name view (https://router.vuejs.org/guide/essentials/named-views.html#named-views)

tmorehouse avatar Mar 04 '20 15:03 tmorehouse

@tmorehouse thanks for your quick reply!

I've just tried out your proposed workaround and it seems that nested views cannot use named views from "upstream" routes/components. It only works when the named <router-view> is defined inside the parent component.

  • see https://codesandbox.io/s/zealous-cloud-wrxdl : The child modal of Page1.vue is not rendered in MainView.vue, but the child modal of Page2.vue is rendered in <router-view name="modal"/> of Page2.vue

boardend avatar Mar 04 '20 16:03 boardend

There is a way to force a particular parent on a component... it requires manual mounting though (note I haven't tested this example code, but I have done similar for other things):

<template>
  <div>
    <router-view ref="parentView" />
    <div ref="portalTarget" />
  </div>
</template>

<script>
import { PortalTarget } from 'portal-vue'

export default {
  mounted() {
    const parentComponent = this.$refs.parentView
    const childElement = this.$refs.portalTarget
    const pt = new PortalTarget({
      el: childElement,
      parent: parentComponent,
      propsData: {
        name: 'modal-portal'
      }
    })
    // Ensure the portal target is destroyed
    this.$once('hook:beforeDestroy', () => pt.$destroy())
  }
}
</script>

EDIT:

This will probably not work since <router-view> is a functional component (which doesn't have a Vue this instance).

You would need to render the portal target inside of the parent router-view, and pass the top route's parent instance to the parent of the dynamically added portal-target. Or one could manually mount the child router-view and pass an explicit parent to it.

tmorehouse avatar Mar 04 '20 16:03 tmorehouse

As you already stated, I couldn’t get the functional router-view component mounted with a manually set parent component. And when I tried mounting it with a wrapper component (as proposed here https://stackoverflow.com/questions/54239343/how-do-you-unit-test-a-vue-js-functional-component-with-a-render-function-that-r/54277077#54277077) Vue Router didn’t pick up the router-view element.

I’m currently perusing a solution without Portal-Vue which works, but is not as nice if I were able to send the route component to the desired location.

If there isn’t a nice/easy solution to get Vue Router and Portal-Vue working together, we should use this issue to add some notes to the Portal-Vue Known Caveats.

boardend avatar Mar 09 '20 08:03 boardend

This is indeed a caveat, which is a consequence of the $parentcaveat.

<router-view> internally walks the $parent chain to find out how many (if any) parent <router-view> components there are.

We should indeed add a note to the docs.

Also note that this is not a problem in vue-simple-portal, and will also not be an issue with Vue 3's native <teleport> component.

LinusBorg avatar Jun 21 '20 10:06 LinusBorg

I've switchd to vue-simple-protal which indeed fixes this problem. Added a small note to the docs anyway, see https://github.com/LinusBorg/portal-vue/pull/307

boardend avatar Jul 23 '20 09:07 boardend

Thanks! Will close then docs change has been deployed.

LinusBorg avatar Jul 24 '20 08:07 LinusBorg