Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/watcheffect-flush-post-for-refs.md
1---2title: Use flush post for watchEffect with Template Refs3impact: MEDIUM4impactDescription: Default watchEffect runs before DOM updates, causing refs to be out of sync5type: gotcha6tags: [vue3, watchEffect, template-refs, flush, dom-timing]7---89# Use flush post for watchEffect with Template Refs1011**Impact: MEDIUM** - By default, `watchEffect` runs before the DOM is updated. When watching template refs, this means the effect may run with stale or null ref values. Use `flush: 'post'` to ensure the effect runs after DOM updates when refs are current.1213This timing issue is particularly confusing because the watcher runs, but the ref doesn't yet reflect the current DOM state.1415## Task Checklist1617- [ ] Use `{ flush: 'post' }` when watchEffect accesses template refs18- [ ] Alternatively, use `watchPostEffect` helper for cleaner syntax19- [ ] Still include null checks as refs can be unmounted20- [ ] Consider using `watch` with explicit ref watching instead2122**Incorrect:**23```vue24<script setup>25import { ref, watchEffect } from 'vue'2627const inputEl = ref(null)28const text = ref('')2930// WRONG: Runs BEFORE DOM update - ref may be null or stale31watchEffect(() => {32// On first run: inputEl.value is null (DOM not rendered yet)33// On updates: May reference old element state34if (inputEl.value) {35console.log('Input value:', inputEl.value.value) // Stale!36inputEl.value.focus()37}38})39</script>4041<template>42<input ref="inputEl" v-model="text" />43</template>44```4546```vue47<script setup>48import { ref, watchEffect } from 'vue'4950const items = ref([1, 2, 3])51const itemRefs = ref([])5253// WRONG: Refs array not yet populated when this runs54watchEffect(() => {55console.log('Number of refs:', itemRefs.value.length) // Always 0!56})57</script>5859<template>60<div v-for="item in items" :key="item" :ref="el => itemRefs.value.push(el)">61{{ item }}62</div>63</template>64```6566**Correct:**67```vue68<script setup>69import { ref, watchEffect } from 'vue'7071const inputEl = ref(null)72const text = ref('')7374// CORRECT: flush: 'post' runs AFTER DOM update75watchEffect(() => {76if (inputEl.value) {77console.log('Input value:', inputEl.value.value) // Current!78inputEl.value.focus()79}80}, { flush: 'post' })81</script>8283<template>84<input ref="inputEl" v-model="text" />85</template>86```8788```vue89<script setup>90import { ref, watchPostEffect } from 'vue'9192const inputEl = ref(null)93const showInput = ref(true)9495// CORRECT: watchPostEffect is shorthand for flush: 'post'96watchPostEffect(() => {97if (inputEl.value) {98inputEl.value.focus()99}100})101</script>102103<template>104<input v-if="showInput" ref="inputEl" />105</template>106```107108```vue109<script setup>110import { ref, watch, onMounted } from 'vue'111112const inputEl = ref(null)113114// ALTERNATIVE: Use watch on the ref directly115watch(inputEl, (el) => {116if (el) {117el.focus()118}119}, { flush: 'post' })120121// ALTERNATIVE: For one-time setup, onMounted is sufficient122onMounted(() => {123inputEl.value?.focus()124})125</script>126127<template>128<input ref="inputEl" />129</template>130```131132```vue133<script setup>134import { useTemplateRef, watchPostEffect } from 'vue'135136// Vue 3.5+ with useTemplateRef137const input = useTemplateRef('my-input')138139// CORRECT: watchPostEffect with useTemplateRef140watchPostEffect(() => {141input.value?.focus()142})143</script>144145<template>146<input ref="my-input" />147</template>148```149150## Flush Options Explained151152```javascript153// Default: 'pre' - runs before DOM update154watchEffect(() => { ... }) // Same as { flush: 'pre' }155156// 'post' - runs after DOM update (use for refs)157watchEffect(() => { ... }, { flush: 'post' })158watchPostEffect(() => { ... }) // Shorthand159160// 'sync' - runs synchronously (rarely needed, can cause issues)161watchEffect(() => { ... }, { flush: 'sync' })162watchSyncEffect(() => { ... }) // Shorthand163```164165## When to Use Each Flush Mode166167| Scenario | Recommended Flush |168|----------|-------------------|169| Accessing template refs | `post` |170| Reading updated DOM | `post` |171| Triggering before render | `pre` (default) |172| Performance-critical sync updates | `sync` (with caution) |173174## Reference175- [Vue.js Watchers - Callback Flush Timing](https://vuejs.org/guide/essentials/watchers.html#callback-flush-timing)176- [Vue.js watchEffect API](https://vuejs.org/api/reactivity-core.html#watcheffect)177