vuejs / router

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

`popstate` event fires again if `onBeforeRouteLeave` callback returns `false` #2279

Closed frederikheld closed 3 months ago

frederikheld commented 3 months ago

Reproduction

I could not create an interactive reproduction as the CodeSandbox link leads to a read-only project and JSFiddle is a totally different approach to how I'm doing Vue.

Steps to reproduce the bug

I'm trying to catch the popstate event to close open modals instead of navigating to the previous view if any modals are open. While doing this, I encountered strange behavior of onBeforeRouteLeave(): if the callback returns false, it will make the popstate event fire again, but only after several milliseconds.

Here's my code:

<script setup lang="ts">
import { ref, onMounted } from 'vue
import { onBeforeRouteLeave } from 'vue-router'

const backButtonNavigationDetected = ref<boolean>(false)

onMounted(() => {
  window.addEventListener('popstate', () => {
    console.log('popstate event listener', Date.now())
    backButtonNavigationDetected.value = true
  })
})

onBeforeRouteLeave(() => {
  console.log('onBeforeRouteLeave', Date.now())
  return false
})
</script>

vue-router doesn't seem to have a feature that allows to check for back button navigation. Therefore I'm listening to the event myself.

If you execute the code, you will notice two things:

  1. onBeforeRouteLeave will fire BEFORE the popstate event.
  2. The popstate event will fire twice if the onBeforeRouteLeave callback returns falls but only once if it returns true

To work around the first issue I came up with an async solution and setTimeout(() => {}, 0) to put the code inside the callback at the end of the call stack. But this is not the issue here.

The issue is that the return value has side-effects with popstate.

I double-checked that it is actually the same event listener and not something from a different view. I also used useEventListener from vue-use to avoid re-registering the same callback again and again. This can't happen though, as the view is kept alive. That mentioned, removing the view from <KeepAlive> makes no difference with this bug though.

Maybe my whole approach is bonkers, but this is what I came up with through trial and error and I think that this behavior of onBeforeRouteLeave is wrong.

Expected behavior

popstate event should only fire once if the back button is only pressed once.

Actual behavior

see above

Additional information

No response