Before or After the Screen Changes: Understanding Flush Timing in Vue Watchers
Vue watcher flush timing controls when a watcher runs relative to state changes and DOM updatesâpre runs before rendering, post runs after the DOM is updated, and sync runs immediatelyâmaking timing crucial for whether your logic depends on data or the rendered UI.
At some point, you write a watcher that looks completely reasonable.
It reacts to state.
It runs when something changes.
And yet⌠something feels off.
Maybe the DOM isnât updated yet.
Maybe it updates twice.
Maybe something runs âtoo earlyâ or âtoo late.â
Itâs subtle.
And the reason sits in a place we rarely think about:
Not what runsâbut when it runs.
Not All Reactions Happen at the Same Time
When you change state in Vue:
count.value++It doesnât immediately update everything.
Instead, Vue begins a process:
- mark components as needing updates
- schedule re-renders
- queue watchers
Thereâs a timeline forming behind the scenes.
And your watcher has to exist somewhere inside that timeline.
That âsomewhereâ is what Vue calls flush timing.
The Default: Before the World Updates
By default, watchers run with:
flush: 'pre'Which means:
Run before the DOM updates
So the sequence becomes:
- state changes
- watcher runs
- DOM updates
At first, this feels natural.
Youâre reacting to data, not the UI.
But thereâs a deeper benefit here.
When you update other reactive state inside the watcher, Vue can batch everything together and render only once.
Youâre not chasing the UI.
Youâre preparing it.
When the DOM Matters More Than Data
Now consider a different situation.
You donât just care about state.
You care about whatâs already rendered.
watch(count, () => {
console.log(el.value.offsetHeight)
}, { flush: 'post' })Here, timing becomes critical.
Because if the watcher runs too early:
- the DOM still reflects the old state
- measurements are wrong
So Vue offers another position in the timeline:
flush: 'post'Now the sequence becomes:
- state changes
- DOM updates
- watcher runs
Youâre no longer preparing the UI.
Youâre reacting to what already exists.
A Third Option: Ignore the Timeline
There is one more mode:
flush: 'sync'This one doesnât wait.
It doesnât schedule.
It doesnât batch.
It runs immediately.
The moment state changes, your watcher executes.
No queue.
No delay.
At first, this feels powerful.
But it comes with a cost.
You lose Vueâs optimization.
If something changes repeatedly, your watcher runs repeatedlyâwithout mercy.
So this mode exists less as a default choice, and more as an escape hatch.
The Same Change, Different Worlds
Take a single update:
count.value++Depending on flush timing, the world rearranges itself.
With pre:
- watcher runs
- then DOM updates
With post:
- DOM updates
- then watcher runs
With sync:
- watcher runs immediately
- DOM updates later
The same line of code.
Three different timelines.
Why This Distinction Exists
Because Vue separates two things:
- reactivity (data changes)
- rendering (DOM updates)
And once those are separate, timing becomes a real question.
Not everything should run at the same moment.
Some logic belongs before rendering.
Some belongs after.
Some bypasses the system entirely.
Choosing Without Memorizing
You donât need to remember all the rules.
You can ask a simpler question.
If your logic depends on data:
Run before rendering â preIf your logic depends on the DOM:
Run after rendering â postIf you need immediate execution and accept the cost:
Run instantly â syncThe choice isnât technical.
Itâs about what your logic cares about.
A Subtle Source of Bugs
Sometimes, watchers donât behave strangely because they are wrong.
They behave strangely because they are early.
Or late.
A measurement happens before the DOM updates.
A state change triggers an extra render.
A side effect runs too often.
And the fix is not rewriting the logicâ
but moving it to the right moment in time.
A Final Thought
Reactivity often feels like:
âWhen something changes, something else runs.â
But Vue is doing something more careful than that.
Itâs not just deciding what should react.
Itâs deciding when that reaction belongs.
And sometimes, that timingâ
more than the logic itselfâ
is what makes everything finally make sense.