VueJS can't update component

Viewed 149

I have this Vue 1 component that gets an object as a prop that has to be filled by the user.

This object has this structure:

{
    property1: ...,
    property2: ...,
    inputs: {
        2130: { value: ..., comment: ... },
        2131: { ... },
        ...,
    }

}

This component is a simple modal with a table containing all necessary inputs. I'd like to check some stuff, make calculations and change styles of cells depending on user input.

When the modal loads I build the page, setting the models of those inputs with their id:

v-model="object.inputs[subgroup[1].dx.mob.id].comment"

(Note: subgroup[1] is part of another object that contains the "definition" of the table structure, by doing inputs[subgroup[1].dx.mob.id] I'm expecting to link the table field to the corresponding input of the object)

The problem is that, whatever I try, the page does not re-render. The <input> fields don't even hold the value, as soon as I click away the it disappears.

More strangely, the values are actually set, but the only way to make this component re-render and do all the nice checks and styling I need him to, is to completely recreate the component through closing and reopening the modal or by setting a v-if on a parent of the table.

These are obviously both non viable options as

  • I need the page to be reactive
  • Recreating the component (at each input change) takes too much time

This are the things I tried:

  • Dynamically build a watcher per input
    • but vm.$watch can't find object for some reason.
...forEach((input, index)=>{
    vm.$watch(`object.inputs[${index}].comment`, function(){
            this.$forceUpdate();
        })
}
  • Return inputs in a computed property and watch that

    • but the page can't detect the changes
  • Manually launch this.$forceUpdate() on @change and @input events

    • but $forceUpdate is not a function
  • Declare a counter in data and increment it on @change and @input events.

    • but the page still does not update.
  • putting a v-if on an invisible object

    • but only said object gets re-rendered
  • changing the :key prop of my component with a data property

    • but nothing happens

The only way I could do this, as anticipated, is by destroying and recreating the component.

Anyone has any solution?

Ugly Solution: After a couple more failed attempts I found something that fixes the problem even if in an improper way. Any real answer is still more than welcome.

Essentially I managed to force the component to re-render by:

  • Declaring a new variable in data, I named it key (in my case it was numeric, but any will do)
  • Creating an update function that switches key from 1 to 0 and vice versa.
  • Binding that function to the @blur event of every input or select
  • Since my whole modal is rendered depending on a computed property, I executed a couple reading operations on key inside this function (In my case I assigned its value to a new variable and then logged it (not sure which of the two triggers the render, but I'd guess the first is enough))
... 
data: function() {
      return {
        ... ,
        key: 0
      };   
},

...

methods: {
    ... ,
    update: function(){
        this.key = this.key == 0 ? 1 : 0
    }
},
computed: {
    ... ,
    testData: function () { //this functions builds an object that is used to render the page  
        var data = []
        var __self = this
        var blu = this.key // This is the line where i read key
        console.log(blu)
        this.test.inputs.forEach(function(input){

            var slug = input.slug.replaceAll('#', '').split('-')
        
            __self.setItem(data, slug, input)

        })
     
        console.log('data', data)
        return data // this value gets then passed to Object.entries() to get an iterable object I build the page with
    } 
}


1 Answers

Better Answer

After an euphemistic bit of debugging I managed to solve this problem.

Essentially the component I described in the question is used in two different pages: a "creation" one and an "editing" one. My mind clicked when I saw that in the editing page everything was working as expected.

What was happening:

The only difference between how the component is used in the two pages is the data that is passed to it. The "editing" page has each object already filled as data already exist, while in the "creation" page each object was obviously blank.

As I needed a usable object I had to fill it with empty inputs so that I could access them later.

I stupidly tried to set the property manually like so:

objects[index].inputs = {}

Expecting that assigning a property to a reactive object would automatically make that property reactive too. Boy was I wrong.

Vue reactive getters and setters:

To make things reactive vue overrides the default Object class getters and setters. But this only happens with specific conditions:

  • The property must be added through Vue.set() or this.$set()
  • All of the parent properties must be reactive as well

For example, after a couple attempts I tried, after setting objects[index].inputs = {}, to use this.$set() to populate my inputs.

This could not work as I didn't make .inputs reactive as well.

So I had to

this.$set('objects[${index}].inputs', {})

before

this.$set(`objects[${index}].inputs[${input.id}].value`, null)
this.$set(`objects[${index}].inputs[${input.id}].comment`, null)

In Conclusion:

If you have reactivity problems my suggestion is to log said object and check if it and its parents have the right getters and setters.

And remember that if you want to add new properties to a reactive object you must $set() it.

Reactive Object:

Reactive Object

Non Reactive Object:

Non Reactive Object

Related