The50
The50

Reputation: 1178

Vue.js updated property doesn't change on child component

I have a child component which is an HTML input element. It looks like this:

<label-input inputId="formProductId"
             inputType="number"
             inputName="productId"
             :inputEnabled="filters.productId.options"
             :label="translations['product_id']"
             :placeholder="translations['product_id']"
             :data="filters.productId.value"
/>

LabelInput.vue file:

<template>
    <div class="form-element">
        <label :for="inputId" :class="['form-element-title', { 'js-focused': focused }]">{{ label }}</label>
        <input @input="updateValue($event.target.value)" :type="inputType" :id="inputId" :name="inputEnabled ? inputName : false" v-model="inputData" :placeholder="placeholder" @focus="focused = true" @blur="focused = false">
    </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class LabelInput extends Vue {
    @Prop({ default: () => '' }) inputId!: string
    @Prop({ default: () => '' }) inputType!: string
    @Prop({ default: () => '' }) inputName!: string
    @Prop({ default: () => '' }) label!: string
    @Prop({ default: () => '' }) placeholder!: string
    @Prop({ default: () => {} }) data!: []
    @Prop({ default: () => true }) inputEnabled!: string

    private inputData = this.data
    private focused = false

    updateValue (value: string) {
        this.$root.$emit('input', {'inputName' : this.inputName, 'inputValue' : value});
    }
}

So the model is passed via filters.productId.value as data and then changed into local variable inputData.

Now if I change the input value I use updateValue function which emits the input name and it's value back into parent component, which updates the original filters.productId.value value. This works fine.

Now the problem is that I have a function on my parent component which resets the filters.productId.value value back to null. It looks like this:

clearFilters() {
    for (const [key, value] of Object.entries(this.filters)) {
        this.filters[key].value = null;
    }
}

And now the data in my child component LabelInput doesn't change, even though filters.productId.value is null now. How do I refresh the child component?

Upvotes: 2

Views: 1874

Answers (1)

el.mano.el
el.mano.el

Reputation: 564

I did not work with Vue as a class component yet, but it seems that the problem is setting the prop to a attribute in the class.

private inputData = this.data

Vue listens to prop changes, but data does not listen for referenced changes, so the prop has changed, but not inputData because Vue doesn't set listeners for it.

Working without classes when I want to do something similar as you, I use the computed properties. Check the docs to see how it is implemented.

Using a computed property.

get inputData() {
  return this.data;
}

I believe this will work as you expect.

To better visualize what we are saying in the comments, here's how the code should look like without v-model.

input:

<input :value="inputData" @input="updateValue($event.target.value)" :type="inputType" :id="inputId" :name="inputEnabled ? inputName : false" :placeholder="placeholder" @focus="focused = true" @blur="focused = false">

Getter:

get inputData() {
  // you can change your prop here and return a new value. 
  // A lower or upper case, for example. Just don't change the prop, only use it.
  return this.data;
}

updateValue:

updateValue(value) {
  this.$root.$emit('input', {'inputName' : this.inputName, 'inputValue' : value});
    }
}

Also, I don't see the need to use $root, since you can use the $emit in the component. For non class components you access it through this.$emit, I believe here should be the same.

Another change you could do is to listen for the event in the html:

<label-input inputId="formProductId"
             inputType="number"
             inputName="productId"
             :inputEnabled="filters.productId.options"
             :label="translations['product_id']"
             :placeholder="translations['product_id']"
             :data="filters.productId.value"
             @updateValue="doSomething"

/>

You can declare a method named doSomething that takes the received value as the first argument or simply set the variable to the value received by the event @updateValue="(value) => {filters.productId.value = value}". This will work as well.

Upvotes: 4

Related Questions