I'm working on a Vue3/Vite Typescript project using <Suspense>
to show a loading state template #fallback
while async child components are being loaded. This works like a charm.
However I would like to have a ref
of one/multiple elements inside my <Suspense #default>
DOM structure (e.g. adding an IntersectionObserver
for some nice animations).
I understand that the "target" ref
is still undefined when the onMounted()
hook is triggered, since this happens when all static content is ready (while the async components are still loading; the "loader" ref
returns the <div class="loader">
). <Suspense>
provides the onResolve()
hook, which is triggered after all contained async components in the template #default
are loaded.
Still the "target" ref
is undefined (there the "loader" ref
returns null - which is ok since it's not shown any more).
Test: If I remove the Introsection
(with the async asset loading) the <Suspense>
directly resolves (before onMounted()
) and the "target" ref
returns the referenced <section>
directly in the onMounted()
hook (in the onResolve()
hook both refs are undefined).
What am I doing wrong? Appreciate any useful hint/solution.
My code (shortend):
import IntroSection from '@/components/IntroSection.vue'
import { useTemplateRef, onBeforeUpdate, onUpdated, onMounted, ref } from 'vue'
const target = ref<Element>() // or useTemplateRef('target')
const loader = ref<Element>()
const animate = ref<boolean>(true)
onMounted(() => {
console.log('onMounted')
console.log(target.value)
console.log(loader.value)
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate')
})
onUpdated(() => {
console.log('onUpdated')
})
function onPending() {
console.log('onPending...')
}
function onResolve() {
console.log('onResolve...')
console.log(target.value)
console.log(loader.value)
}
function onReject() {
console.log('onReject')
}
</script>
<template>
<Suspense :onPending="onPending" :onResolve="onResolve" :onReject="onReject">
<template #default>
<div>
<!-- Spacer Section -->
<section class="header_padding">
<div class="container">
<div class="row title"></div>
</div>
</section>
<!-- End Spacer Section -->
<!-- Intro Section with async asset loading -->
<IntroSection>
<div class="row title">
<h1 class="header_headline_style_2">
HEADLINE
</h1>
<p>
<br />
Some Text here
</p>
<br />
</div>
</IntroSection>
<!-- End Intro Section -->
<!-- Service Section -->
<section ref="target">
<div v-if="animate" class="nf-item spacing">
<div class="box_module_1">
<div class="box_module_2">
<h2 class="h2_box">TITLE</h2>
<p>TEXT</p>
</div>
<div class="box_module_2">
<RouterLink to="/text" class="btn">
BUTTONTEXT
</RouterLink>
</div>
</div>
</div>
</section>
<!-- End Service Section -->
</div>
</template>
<template #fallback>
<!-- Preloader -->
<section id="preloader">
<div class="loader" id="loader" ref="loader">
<div>Loading...</div>
<div class="loader-img"></div>
</div>
</section>
<!-- End Preloader -->
</template>
</Suspense>
</template>
To access a ref
within a <Suspense>
component after async components have loaded, use the nextTick
function within the onResolve
hook to ensure the DOM has updated. Ensure your ref
is correctly assigned to the intended element. Debugging with console.log
in onResolve
can help verify the timing and availability of your ref
. This approach helps manage the asynchronous nature of components and their impact on Vue's reactivity system.
import { nextTick } from 'vue';
function onResolve() {
nextTick(() => {
console.log(target.value); // Now, target should be defined
});
}