vuejs / router

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

`beforeRouteEnter` does not have an injection context if component is not yet loaded #2051

Closed rijenkii closed 8 months ago

rijenkii commented 11 months ago

Reproduction

https://jsfiddle.net/rv3p5ajn/13/

Code, if JSFiddle is down ```html
url: {{ $route.fullPath }}
  • Page 1
  • Page 2
```

Steps to reproduce the bug

  1. Open JSFiddle console
  2. Click "Page 1"
  3. Click "Page 2"
  4. Click "Page 1" again
  5. Click "Page 2" again

Expected behavior

After all clicks value of hasInjectionContext() should be true.

Actual behavior

On first and second clicks hasInjectionContext() is false. On third and fourth, when promises are already resolved, hasInjectionContext() is true.

Additional information

According to the docs, all guards should have an injection context.

All other guards do have an injection context, but only before the first yield.

I think the problem lies somewhere within runWithContext, because it behaves weirdly with promises:

const { createApp, hasInjectionContext } = Vue;
const app = createApp({});

app.runWithContext(async () => {
  console.log(hasInjectionContext());
  await new Promise((r) => setTimeout(r));
  console.log(hasInjectionContext());
});

Output is true, then false -- context is reset after first yield. Maybe the bug is in vuejs/core?

EDIT: Turns out that the weird behaviour of runWithContext with async functions is known and kind of documented in Nuxt's docs: https://nuxt.com/docs/api/composables/use-nuxt-app#runwithcontext

posva commented 11 months ago

It seems that because of lazy loaded routes, the actual guard is not executed with the context anymore. Once the component is lazy loaded it's no longer loaded again, that's why it only happens with beforeRouteEnter.

posva commented 11 months ago

I created https://github.com/vuejs/router/pull/2053 but it still needs tests. Feel free to add them. As a workaround you can force a redirect if the injectioncontext is not there:

{
    template: `<div>page 1</div>`,
    beforeRouteEnter: (to) => {
        if (!hasInjectionContext()) {
        return to.fullPath
    }
    console.log(hasInjectionContext())
    },
  }