reactive object not updating on event emitted from watch

Viewed 1000

i'm building a complex form using this reactive obj

    const formData = reactive({})
    provide('formData', formData)

inside the form one of the components is rendered like this:

      <ComboZone
          v-model:municipality_p="formData.registry.municipality"
          v-model:province_p="formData.registry.province"
          v-model:region_p="formData.registry.region"
        />

this is the ComboZone render function:

     setup(props: any, { emit }) {
        const { t } = useI18n()
    
        const { getters } = useStore()
        const municipalities = getters['registry/municipalities']
        const _provinces = getters['registry/provinces']
        const _regions = getters['registry/regions']
    
        const municipality = useModelWrapper(props, emit, 'municipality_p')
        const province = useModelWrapper(props, emit, 'province_p')
        const region = useModelWrapper(props, emit, 'region_p')
    
        const updateConnectedField = (key: string, collection: ComputedRef<any>) => {
          if (collection.value && collection.value.length === 1) {
            console.log(`update:${key} => ${collection.value[0].id}`)
            emit(`update:${key}`, collection.value[0].id)
          } else {
            console.log(`update:${key} =>undefined`)
            emit(`update:${key}`, undefined)
          }
        }
    
        const provinces = computed(() => (municipality.value ? _provinces[municipality.value] : []))
        const regions = computed(() => (province.value ? _regions[province.value] : []))
    
        watch(municipality, () => updateConnectedField('province_p', provinces))
        watch(province, () => updateConnectedField('region_p', regions))
    
        return { t, municipality, province, region, municipalities, provinces, regions }
      }

useModelWrapper :

import { computed, WritableComputedRef } from 'vue'
export default function useModelWrapper(props: any, emit: any, name = 'modelValue'): WritableComputedRef<any> {
  return computed({
    get: () => props[name],
    set: (value) => {
      console.log(`useModelWrapper update:${name} => ${value}`)
      emit(`update:${name}`, value)
    }
  })
}

problem is that the events emitted from useModelWrapper update the formData in the parent template correctly, the events emitted from inside the watch function are delayed by one render....

3 Answers

TL;DR;

Use watchEffect instead of watch


...with the caveat that I haven't tried to reproduce, my guess is that you're running into this issue because you're using watch which runs lazily. The lazy nature is a result of deferring execution, which is likely why you're seeing it trigger on the next cycle.

watchEffect

Runs a function immediately while reactively tracking its dependencies and re-runs it whenever the dependencies are changed.

watch

Compared to watchEffect, watch allows us to:

  • Perform the side effect lazily;
  • Be more specific about what state should trigger the watcher to re-run;
  • Access both the previous and current value of the watched state.

found a solution, watchEffect wasn't the way. Looks like was an issue with multiple events of update in the same tick, worked for me handling the flush of the watch with { flush: 'post' } as option of the watch function.

Try to use the key: prop in components. I think it will solve the issue.

Related