Suspended component still trigger watchers while being switched out
Vue version
latest
Link to minimal reproduction
Steps to reproduce
- click the button
What is expected?
only one watcher should trigger
What is actually happening?
both, the watcher of the leaving and entering component are benig triggered
System Info
No response
Any additional comments?
In this scenario the update of the variable being watched is what triggers the switch
Is this just a race condition? Since the old component still needs to display, I would say it's normal for it to render due to watchers. This would require vue router to have a special handling of the current route so
let me make a supplement,in Vue document
When a revert happens, fallback content will not be immediately displayed. Instead,
will display the previous #default content while waiting for the new content and its async dependencies to be resolved.
"
To avoid this situation,use timeout=0 prop,as follow:
<Suspense timeout=0>
<component :is="route" />
</Suspense>
@moushicheng @posva This wont do, because original issue arose from a vue-router use case. When I'm navigating between pages, I want the old page to be displayed, while new page is being loaded in the background thanks to Suspense. Unfortunately this is currently impossible in vue-router, because the old page keeps responding to effects/watchers while being switched out.
I'm quite baffled by this, because I don't know how to fetch data in pages based on route parameters, so that old pages don't trigger re-fetch while navigating to different page. Nuxt has the same problem.
You can check the data fetching documentation in vue router, there are a few examples. There is also an ongoing RFC for a more advanced data fetching api in the rfcs repo
@posva Thanks, I looked into both, but unfortunately neither is sufficient for us.
First, about the data fetching in the Vue Router documentation. Data fetching in beforeRouteEnter or in the created method are kinda meh since Vue 3 with setup came. I need application context for the data fetching (is the user signed? what config is in the store? what cookies are set? what route parameters are there?...) which are only accessible via composables in the setup method. (BTW, wishful thinking, it would be great if we could use composables relying on app context (provide/inject) in route guards).
Fetching in created is now even less desirable since introduction of Suspense.
Here is a unit test:
test('post flush watchers in toggled components', async () => {
let cnt = 0
const CompA = {
template: `<div>A</div>`,
setup: async () => {
const route = inject<any>('route')
watch(
() => route.value,
() => cnt++,
{ immediate: true, flush: 'post' }
)
}
}
const CompB = {
template: `<div>B</div>`,
setup: async () => {
const route = inject<any>('route')
watch(
() => route.value,
() => cnt++,
{ immediate: true, flush: 'post' }
)
}
}
const route = shallowRef(CompA)
const Parent = {
template: `
<Suspense>
<Component :is="route" />
</Suspense>
`,
setup: () => {
provide('route', route)
return { route }
}
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
// wait for flush
await nextTick()
// wait for child async setup resolve
await nextTick()
expect(serializeInner(root)).toBe(`<div>A</div>`)
expect(cnt).toBe(1)
route.value = CompB
// wait for flush
await nextTick()
// wait for child async setup resolve
await nextTick()
expect(serializeInner(root)).toBe(`<div>B</div>`)
expect(cnt).toBe(2)
})
@posva Hey. So I created a PR with a fix here: https://github.com/vuejs/core/pull/7009 . Could you please look at it and ideally merge it?