2

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?

2
  • it seems the issue was caused by this.data didn't sync to this.inputData when parent component changed the props=data. so one solution is add one watch for this.data, when its value is changed, let this.inputData=this.data in your LabelInput.vue Commented Aug 31, 2020 at 17:46
  • But LabelInput component doesn't know when data was changed. How do I know when I should set it? Commented Aug 31, 2020 at 17:49

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

8 Comments

Nice, it looks like it's working now. But what about the setter? How it should look like? I can't set this.data it it because I get a warning about mutating a property directly. Or should I leave the setter empty?
You can use set inputData() { // emit the event to parent component here }. Here is well described.
I see you are using v-model and @input, I don't see the need to use both. I believe you can use just v-model with the getter and setter and emit the event inside the setter.
Another option is to use :value=inputData and the @input event and don't use v-model. This won't trigger the warning about mutating a property directly. This is how I usually treat data inside components with inputs and it works amazingly. It's makes very clear that the child component's job is just to receive the data the user inputs and send it to the responsible (in this case parent) component.
Yes, you need to use the emit either way to send the data to the parent component. Your input should look like: <input :value="inputData" @input="updateValue(...)">, your methods: get inputName() {return this.data} updateValue(val) {// emit here}. This way, your child component receives the data from the parent, displays it and your updateValue sends it to the parent component, which sends the value to the child again. This way the child receives the data to display it and does not change it. Only in the parent component you need to set filters.productId.value=value to change the data.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.