I want to handle a v-model
change based on an @input
variable update, but I'm facing issues with the order of "happenings" in vue. Here is a simplified version of my issue:
<script setup>
import { ref, watch } from 'vue'
const foo = ref(1)
const bar = ref(1)
const lorem = ref(false)
const handleInput = (val) => {
bar.value = val
}
watch(foo, (newFoo) => {
if (bar.value === 2) {
lorem.value = true
}
})
</script>
<template>
<input v-model="foo" @input="handleInput(2)"><br>
{{foo}}<br>
{{bar}}<br>
{{lorem}}
</template>
It seems, that the watcher hits before the handleInput
method was executed.
If I change the value of foo
in the input field, the lorem
value will be set to true
if I change the value of foo
a second time, only. I want to set it to true
immediately.
I tried to implement nextTick()
and flush: 'post'
, but it does not have the expected result:
<script setup>
import { ref, nextTick, watch } from 'vue'
const foo = ref(1)
const bar = ref(1)
const lorem = ref(false)
const handleInput = (val) => {
nextTick(() => {
bar.value = val
})
}
watch(foo, async (newFoo) => {
await nextTick()
if (bar.value === 2) {
lorem.value = true
}
}, { flush: 'post' })
</script>
<template>
<input v-model="foo" @input="handleInput(2)"><br>
{{foo}}<br>
{{bar}}<br>
{{lorem}}
</template>
Here is a Vue Playground showing my issue.
In the Vue's code there are "jobs" executed seemingly as microtasks and the DOM update following.
nextTick()
:
A utility for waiting for the next DOM update flush.
In our case the changing of an input value doesn't queue any DOM update so nextTick()
is scheduled immediately as a microtask. Using 2 await nextTick();
also doesn't help... Neither {flush:post}
...
So the solution is to use more a radical way to postpone the watch's code, setTimeout()
comes to mind, but I've tried requestAnimationFrame
and it works, I like it better because it allows execution right after all microtasks and before the page repaint.
But actually you don't need this trick, just place all of your logic inside handleInput:
<script setup>
import { ref, watch , reactive, nextTick} from 'vue'
const foo = ref(1)
const bar = ref(1)
const lorem = ref(false)
const log = reactive([]);
const handleInput = (val) => {
bar.value = val
lorem.value = bar.value === 2;
log.push(`bar: ${val} lorem: ${lorem.value} foo: ${foo.value}`);
}
</script>
<template>
<input v-model="foo" @input="handleInput(2)"><br>
<input v-model="foo" @input="handleInput(1)"><br>
{{foo}}<br>
{{bar}}<br>
{{lorem}}
<div v-for="line in log">
{{line}}
</div>
</template>