Home > Net >  TypeError: Cannot read properties of undefined (reading '$refs') with VueJS
TypeError: Cannot read properties of undefined (reading '$refs') with VueJS

Time:12-30

Parent Component:

...
<v-stepper-step
:rules="[()=>isValid(1)]"
>
    MyStep
</v-stepper-step>
<v-stepper-content>
    <Mytag ref="MyReference" />
</v-stepper-content>
...
methods: {
    isValid(number){
      return mySwitch(number)
 }
    mySwitch(number){
      let v = false
      switch(number){
        case '1':
        v = this.$refs.MyReference.$refs.MyTagForm.validate()
        break
      }
     return v
 }
}

Child Component:

...
<v-form
  ref="MyTagForm"
>
...
</v-form>
...

Following problem: as soon as the page is loaded I get the TypeError: Cannot read properties of undefined (reading '$refs'). BUT as soon as the page is loaded and I change between steps everything with the validation is fine.

I think the problem is that the reference just doesn't exist at the very beginning. I tried to use setInterval and setTimeout around the method in isValid and in mySwitch - but then the validation is always false.

I hope someone of you can help.

CodePudding user response:

The problem is that in Vue.js, a component is rendered through a particular set of stages--for instance, a component is first created before it is mounted--and you're attempting to retrieve a reference to a rendered DOM object that has not yet been rendered by the framework. In other words, you're trying to retrieve something that doesn't yet exist (as you suspected).

In order to resolve this problem, you have to account for the child component not yet being rendered, choosing instead to defer validation until the component has been mounted. One such option would be to have the child component signal to the parent when it has successfully mounted and return a default validation value of true or false until then. This example should illustrate the idea:

...
<v-stepper-step
:rules="[()=>isValid(1)]"
>
    MyStep
</v-stepper-step>
<v-stepper-content>
    <Mytag ref="MyReference" v-on:ready="childReady = true" />
</v-stepper-content>

{
  ...
  data() {
    return {
      childReady: false
    }
  },
  methods: {
    isValid(number){
      return mySwitch(number)
  }
  mySwitch(number){
    // Prevents the normal switch code from running until the child component mounts.
    if(!this.childReady) {
      return false;
    }

    let v = false
    switch(number){
      case '1':
      v = this.$refs.MyReference.$refs.MyTagForm.validate()
      break
    }
    return v
  }
}
...
<v-form
  ref="MyTagForm"
>
...
</v-form>
...

{
  ...
  mounted() {
    // The mounted lifecycle hook will signal the "ready" event when this component is rendered, allowing the parent to know that this component has finished rendering.
    this.$emit('ready');
  }
}

There are, of course, other ways that you can manage this, and I would argue that there are multiple much better solutions than the one I've illustrated above. That said, I wanted to keep this answer simple; the above should hopefully be sufficient for aiding you in grasping what the underlying problem is and giving you the starting point for better designing the solution you want in your code.

  • Related