Documenting scenarios encountered when using DOM elements in immediately executed watch
with Vue3 setup syntax.
Recommended Solution
Requirement: After DOM element exists, perform DOM operations whenever certain data changes. For example, when using third-party charts, we need to update the chart based on data changes after the element exists.
<div ref="el"></div>
const el = ref();
const data = computed(() => "your data");
watch(
() => [el.value, data.value],
() => {
if (!el.value) return;
// DOM operations
console.log(document.body.clientHeight);
},
{ immediate: true }
);
Some might place watch inside onMounted to ensure DOM is loaded. While this works, it's risky. Watches not executed synchronously in setup or after any await won't be properly associated with the component by Vue, potentially causing memory leaks when the component is destroyed (though you can manually stop such watches - see official docs).
Experiment
<div ref="el"></div>
const el = ref();
const data = computed(() => "your data");
watch(
() => [el.value, data.value],
async () => {
if (!el.value) return;
// Get body height
console.log(document.body.clientHeight);
await $nextTick();
// Get body height again
console.log(document.body.clientHeight);
},
{ immediate: true }
);
Body Height Experiment | First | After $nextTick |
---|---|---|
Vue | Height is 0 | Normal height |
Nuxt | Error: document undefined | Normal height |
Nuxt 不开启 ssr | Height is 0 | Height is 0 |
Nuxt Solution
I designed this experiment because I encountered issues with Echarts which requires non-zero element dimensions.
From the results, Vue and SSR-enabled Nuxt behave as expected, while non-SSR Nuxt shows unexpected behavior. The solution is simple: regardless of SSR status, wrap DOM elements (like charts, sliders, video players) needing operations with Nuxt's built-in client-only component. This ensures proper dimensions when the element becomes available.
<client-only>
<div ref="el"></div>
</client-only>
ref in v-for
<div v-for="...">
<div ref="el"></div>
</div>
In this case, el.value will be an array - be aware of this behavior.