How can I prevent watched elements from calling updates on each other in Vuejs?
const width = ref(0);
const height = ref(0);
watch(width, async (newItem, oldItem) => {
console.log(`width: ${oldItem}->${newItem}`);
height.value = newItem / 2;
});
watch(height, async (newItem, oldItem) => {
console.log(`height: ${oldItem}->${newItem}`);
width.value = newItem * 2;
});
as a result, I can see output like this in the console
width: 1->2
height: 0.5->1
width: 2->2
Full vuejs code:
<script setup>
import { ref } from 'vue'
import { watch } from 'vue'
const width = ref(0);
const height = ref(0);
watch(width, async (newItem, oldItem) => {
console.log(`width: ${oldItem}->${newItem}`);
height.value = newItem / 2;
});
watch(height, async (newItem, oldItem) => {
console.log(`height: ${oldItem}->${newItem}`);
width.value = newItem * 2;
});
</script>
<template>
<div class="item">
<div class="details">
<h3>
Width {{ width }}
</h3>
<input v-model="width" placeholder="0" />
</div>
</div>
<div class="item">
<div class="details">
<h3>
Height {{ height }}
</h3>
<input v-model="height" placeholder="0" />
</div>
</div>
</template>
If you output the values with JSON.stringify
you'll notice that the type is changed from string to number due calculations:
Playground
width: 0->"2"
height: 0->1
width: "2"->2
Just use v-model.number="width"
to operate on numbers only: Playground
To avoid entering non-numeric values I would suggest to use keydown
event:
const preventAlpha = e => {
if(/[^\d.]/.test(e.key) && e.key.length === 1){
e.preventDefault();
} else
if(e.key === '.' && e.target.value.includes('.')){
e.preventDefault();
} else
if(e.target.value === '' && e.key === '.'){
e.target.value = '0';
}
}
const vNumber = {
mounted: el => el.addEventListener('keydown', preventAlpha),
unmounted: el => el.removeEventListener('keydown', preventAlpha)
}
UPDATE Answer the comment: there's usually no need to prevent the second watch since it's settled after and no further watches are invoked. Otherwise I don't know a way to change a ref silently, but you could make manual checks:
let changing = false;
watch(width, async (newItem, oldItem) => {
if(changing) return;
console.log(`width: ${JSON.stringify(oldItem)}->${JSON.stringify(newItem)}`);
changing = true;
height.value = newItem / 2;
await 1;
changing = false;
});
watch(height, async (newItem, oldItem) => {
if(changing) return;
console.log(`height: ${JSON.stringify(oldItem)}->${JSON.stringify(newItem)}`);
changing = true;
width.value = newItem * 2;
await 1;
changing = false;
});