Home > Back-end >  Why does watchEffect trigger while the watched value is not changed?
Why does watchEffect trigger while the watched value is not changed?

Time:12-07

I have a component which keeps a local partial copy of an Pinia storage data.

<template>
  <h3>Order idx {{ idx }}: sum = {{ localSum }}, store sum = {{ getOrderSum() }}</h3>
  <input type="number" v-model="localSum" />
  <button @click="updateSum">Save</button>
</template>

<script setup>
import { useCounterStore } from '../store/counter'
import { watchEffect, ref, defineProps, watch } from 'vue'

const props = defineProps({
  idx: 0
})

const store = useCounterStore()

const localSum = ref(0)

function getOrderSum() {
  return store.getOrderIdxSumMap[props.idx]
}

function updateSum() {
  store.setOrderSum(props.idx, localSum.value)
}

watch(
  () => getOrderSum(),
  (newValue) => {
    console.log('i am updated')
    localSum.value = newValue
  }, {
    immediate: true,
  }
)

/*
watchEffect(() => {
  console.log('i am updated')
  localSum.value = getOrderSum()
})
*/
</script>

Whenever external data changes the local copy should update. Using watchEffect instead of watch causes components with modified and unsaved data to lose user input.

watchEffect behaviour description 1:

  1. Change first order data
  2. Click save
  3. You'll see i am updated twice within console.

watchEffect behaviour description 2:

  1. Change the first order data
  2. Change the second order data
  3. Click save on the first order
  4. You'll see the second order changes lost

Comment out watchEffect and uncomment watch. Now everything works just fine. Is it my misconseptions or a bug worth to be reported?

Full demo

CodePudding user response:

It is how watch and watchEffectsupposed to work

In short, watch and watchEffect both tracks the change of their dependencies. When the dependencies changed they act like follow:

  • watch recalculates its source value and then compares the oldValue with newValue. If oldValue !== newValue, it will trigger the callback.
  • watchEffect has no sources so it triggers the callback anyway

In your example the dependencies of both watch and watchEffect are store.getOrderIdxSumMap and props.idx. The source of watch is the function () => getOrderSum().
So when the store.getOrderIdxSumMap changed, watchEffect will always trigger the callback but watch will re-calculate the value of () => getOrderSum(). If it changed too, watch will trigger its callback

When Vue detects a dependency is changed?

Whenever you set a value for reactive data, Vue uses a function to decide if the value is changed as follows:

export const hasChanged = (value: any, oldValue: any): boolean =>
  !Object.is(value, oldValue)

So setting the same value for a primitive type (number, string, boolean...) will not be considered as a value change

  • Related