Skip to content

route change should not be changing state until the previous component has been unmounted #3393

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

Closed
tstriker opened this issue Nov 28, 2020 · 16 comments

Comments

@tstriker
Copy link

tstriker commented Nov 28, 2020

What problem does this feature solve?

When navigating between routes, the changes to state happen before the previous component has been unmounted.

This results in recalculation of any computed properties that rely on the state, even though we'll remove this component a millisecond later.

Route change should fire all relevant navigation signals, but it shouldn't change the state until the old component has been unmounted, as otherwise it's creating side-effects

What does the proposed API look like?

the step 11 "dom updates triggered" here https://next.router.vuejs.org/guide/advanced/navigation-guards.html#the-full-navigation-resolution-flow should be split up into
unmount old component -> update route state -> mount new component

@posva
Copy link
Member

posva commented Nov 28, 2020

This results in recalculation of any computed properties that rely on the state, even though we'll remove this component a millisecond later.

Since the component gets unmounted at the same time the route changes, computed properties are not computed again when navigating away from a route.

Unfortunately what you are proposing is not possible because the route change is what triggers the change of the component rendered by router view

@posva posva closed this as completed Nov 28, 2020
@tstriker
Copy link
Author

@posva - i think you misunderstood me - the properties are being recomputed and watchers are re-triggered, and that's the exact problem.

@adam-coster
Copy link

I've also just run into this. @tstriker Did you find a resolution?

@tstriker
Copy link
Author

tstriker commented Apr 6, 2021

No proper resolution downstream. I've been using this workaround where it's needed - wherever i'm depending on the state params i'm checking if we're still in the same state. Stuff like this:

 if (!["route-a", "route-b"].includes(this.$route.name)) {
    // we are navigating away
    return;
}

I believe the proper solution is still in vue-router as vue router is the one in control of state changes - since it is logically possible it is not technically impossible, so one might hope @posva could take another look as this is not the case with other non-vue routers.

@adam-coster
Copy link

@tstriker Thanks! That's what I started doing as well. I suspect I'll have a lot of these to fix after the transition to v4...

@tstriker
Copy link
Author

tstriker commented Jul 28, 2021

update on this - found a more generic way:

routeChange(component) {
    // on routeChange the matched component won't match the passed in component anymore
    return component.$.type.name != component.$route.matched[0]?.components.default.name;
}

then in the component code can do

if (routeChange(this)) {
    // bail out
    return;
}

the proper fix still would be the destroying of the to-be-unmunted component, of course

@nkhdo
Copy link

nkhdo commented Oct 1, 2021

I'm facing this issue too. Is there any plan to fix it?
More context: I'm using vue compat build to upgrade a big project to vue 3. This issue happens on some specific routes, while doesn't happen on others, and I haven't figured out the different pattern between them yet.

Edit: found the pattern. If I create a watcher for the computed value from $route.params, this computed value will be recalculated after navigating out, hence, the watcher will be triggered too

{
    computed: {
        id() {
            return parseInt(this.$route.params.slug)
        }  
    },
    watch: {
        id: {
            async handler(val) {
                // do something, eg fetch data
            }
        }
    }
}

@sukwony
Copy link

sukwony commented Oct 20, 2021

export default defineComponent({
  watch: {
    $route (val, oldVal) {
      console.log('1', val.params, oldVal.params)
    }
  },
  beforeUnmount () {
    console.log('2')
  }
})

I don't want to see log '1' when leaving this component, but currently I'm seeing '1'

@FrankFang
Copy link

const route = useRoute()
let stopWatch: WatchStopHandle
onMounted(() => {
  stopWatch = watch(() => route.params.id, async (projectId) => {
    if (route.name !== 'ShowProject') { return }
    const response = await httpClient.get(`/projects/${projectId}`)
    project.value = response.data.resource
  }, { immediate: true })
})
onUnmounted(() => stopWatch())

For Vue 3.2 users.

@xuxucode
Copy link

use flush: 'post' to prevent watch effect when unmounting component for Vue3

watch(() => route.params.id, id => load(id), { immediate: true, flush: 'post' });

@matijakevic
Copy link

@posva I think this is still an issue and is still happening in the latest version.

Since the component gets unmounted at the same time the route changes, computed properties are not computed again when navigating away from a route.

This is not the behavior I am getting (ie computed values on page A still get updated moment before the router switches to page B).

Can you confirm?

@kadiryazici
Copy link

kadiryazici commented Oct 27, 2022

In our project we have this code

const project = computed(() => propertiesStore.getProjectByQuery({
  spaceQuery: { slug: route.params.spaceSlug },
  projectQuery: { slug: route.params.projectSlug },
}));

And then we watch this value to fetch data but, when we leave the page project.value changes and watcher gets triggered.

watch(project,getData);

For a workaround we currently use this:

export function watchUntilRouteLeave(source, callback, options) {
  const stop = watch(source, callback, options);
  onBeforeRouteLeave(stop);
  return stop;
}
watchUntilRouteLeave(project, getData);

onBeforeRouteLeave is triggered before route.params change.

@achaphiv
Copy link

achaphiv commented Jan 13, 2023

I ran into this while migrating from vue 2 to vue 3.

My current hack is:

function useRouteParamDirect(name: string): Ref<string> {
  const route = useRoute()
  return computed(() => {
    const maybe = route.params[name]
    if (!maybe) {
      throw new Error(`Missing param: ${name}`)
    }
    return String(maybe)
  })
}

export function useRouteParam(name: string): Ref<string> {
  const actual = useRouteParamDirect(name)
  const validChanges = shallowRef<string>()
  watchEffect(() => (validChanges.value = actual.value), {
    flush: 'post',
  })
  return computed(() => validChanges.value || actual.value)
}

So my components can just do

<script setup lang="ts">
import { useRouteParam } from '@/my-util'

const id = useRouteParam('id')
</script>

without having to deal with undefined.

Surely there's a better way...

@lucastraba
Copy link

This was reported in 2020, closed immediately, and not reopened after it's clear that it's still an issue for many people.

I'm facing the same issue in my migration to Vue 3, and I guess I'm going to have to do the monkey-patching as well.

Can we get an update on this, please?

@tstriker
Copy link
Author

tstriker commented Apr 25, 2023

@lucastraba if you have the time and energy, you could re-file the bug here https://github.com/vuejs/router/issues

you'll need to create a reproducible code as it seems to keep falling on deaf ears (my most recent attempt at reviving the bug from last year: vuejs/router#1590 (comment))

@posva
Copy link
Member

posva commented Jul 29, 2023

AFAIK this is not a problem in Vue router 4. There have been other big reports and they have been addressed, in router and core. One of them require watchers to use a flush post, so you might as well try that.

If after giving these other issues a try you can still reproduce the problem, open a new issue but with a reproduction so I can take a look

@vuejs vuejs locked and limited conversation to collaborators Jul 29, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests