Home > Blockchain >  Reactivity lost: How to use a "deep" computed setter
Reactivity lost: How to use a "deep" computed setter

Time:09-17

I got a modal component that receives an object as v-model. This object contains some keys like id or name.

Now if I do this:

<template>
    <div class="h-2/3 w-2/3 bg-green-500">
        <input v-model="computedValue.id" />
        {{computedValue}}
    </div>
</template>
<script>

export default {
    name: 'TestModal',
    props: {
        modelValue: {
            type: Object,
        },
    },
    emits: ["update:modelValue"],
    data: () => ({
    }),
    computed: {
        computedValue: {
            get() {
                return this.modelValue;
            },
            set(newValue) {
                console.log("computedValue?")
                this.$emit('update:modelValue', newValue)
            }
        },
    },
    methods: {
    },
}
</script>

the console.log("computedValue?") is never triggered and also the emit is not fired.

How can I use a "deep setter"?

CodePudding user response:

The approach I use for this is as follows. Take an initial 'copy' of the property as a data variable (rwModel). Use v-model to interact with this variable, then use a watcher to fire the event/do some work whenever the variable changes.

The advantage is that you never try to modify the property, but you also do the minimum to avoid this, so it's remains easy to test and understand. You can also get the old value in the watcher, which makes doing comparisons easy.

Alternatively instead of watching you can easily modify this to have an event that triggers to do something with the updated data - like a save button on a form.

<template>
    <div class="h-2/3 w-2/3 bg-green-500">
        <input v-model="rwModel.id" />
        {{rwModel}}
    </div>
</template>
<script>

export default {
    props: {
        modelValue: {
            type: Object,
        },
    },
    emits: ["update:modelValue"],
    data() {
      return {
        rwModel: this.modelValue,
      }
    },
    watch: {
      modelValue: {
        deep: true,
        handler(newValue) {
          console.log("watchedValue?")
          this.$emit('update:modelValue', newValue)
        },
      },
    },
}
</script>

CodePudding user response:

What's happening: On an <input> element, v-model="property" is essentialy equivalent to
:value="property" @input="property = $event",

which in your code translates to
:value="computedValue.id" @input="computedValue.id = $event".

This will set the id, but the computed setter will never trigger since you're setting the computedValue.

Solution: Since you want to use an inner property, what you want is a finer control over the @input event. So you can simply replace your input's
v-model="computedValue.id" with
:value="computedValue.id" @input="computedValue = $event"

This will trigger your computed setter and print the console statement.

Note: Even though computedValue = $event makes it look like we're setting computedValue to input's value, it isn't so. Since we've written a setter for the computed property, Vue intelligently triggers the setter when the statement is executed.

  • Related