tx291
tx291

Reputation: 1331

this.$refs always empty or undefined in Vue component

I am new to Vue and am trying to use $refs to grab some elements in the DOM from a sibling component (for very basic purposes, just to get their heights, etc.), and I'm doing so in a computed.

No matter what I try, this.$root.$refs either always comes back undefined or as an empty object, and I don't know what I'm doing wrong.

In a parent component, I have:

<template>
<ComponentA />
<ComponentB />
</template>

In component A I have:

<template>
  <div id="user-nav">
   <div ref="nav-container">
    <slot />
   </div>
  </div>
</template>

I just try to see if I can access this in ComponentB by console logging

 console.log(this.$root.$refs);

in that component's mounted function.

But I keep getting an empty object.

Can you just not access things across sibling components like this???

Upvotes: 7

Views: 28667

Answers (7)

Fathy
Fathy

Reputation: 413

Ok I faced this problem in 2 situations

1- mounted hook was inside methods which is worst typo

2- ref was inside a v-for loop which can not really be initialized without for loop being started in my case the for loop array was empty

Upvotes: 0

Alex H.
Alex H.

Reputation: 1

I had the same problem but the cause was another one:

I had a v-if condition on the referenced element. So at the time of mounting the element was not rendered because the condition was still false.

After 3 hours of looking for the cause for the problem, it was so obvious.

<template>
  <sub-component v-if="showCondition" ref="componentName">
    <div>someContent</div>
    <div>someMoreContent</div>
  </sub-component> 
</template>

<script lang="ts">
export default class MyComponent extends Vue {
  private showCondition = false; // set to true after a REST request
  mounted() {
    console.log(this.$refs); // this was empty
  }
}
</script>

This problem can be solved by moving the v-if to an additional template element inside the referenced component.

<template>
  <sub-component ref="componentName">
    <template v-if="showCondition">
      <div>someContent</div>
      <div>someMoreContent</div>
    </template>
  </sub-component>
</template>

<script lang="ts">
export default class MyComponent extends Vue {
  mounted() {
    console.log(this.$refs); // now 'componentName' is available
  }
}
</script>

Upvotes: 0

Ludolfyn
Ludolfyn

Reputation: 2085

You might have already solved this, but just to answer this Q:

I had a similar problem and it seems that this happens because the function is being called before Vue has had time to replace the root DOM with its own version. What you can do to fix this is to create a mounted life-cycle hook and call the function there.

const vm = new Vue({
  el: '#app',
  data: {
    sayHi: 'Hello World',
  },
  mounted: function() { // The hook. Here, Vue has already worked its magic.
    console.log('YOUR ELEMENT ----> ', this.doSomething())
  },
  methods: {
    doSomething: function() {
      return this.$refs['nav-container']
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <span ref="nav-container">{{ sayHi }}</span>
</div>

I found the solution here.

Upvotes: 7

aln_buber
aln_buber

Reputation: 11

In my specific case the

this.$refs 

part referenced to several components. Accessing a specific component by reference was possible:

this.$refs[componentName]

To access a specific field of the component above I had to select the first observable:

this.$refs[componentName][0].someFieldToBeSelected -> this works
this.$refs[componentName].someFieldToBeSelected -> this fails

.

Upvotes: 0

thoni56
thoni56

Reputation: 3335

This might not be an answer to your particular problem but I discovered that another reason for $refs being empty is that the component on which it is defined has not created/mounted yet. Probably due to the lazy rendering Vue uses.

I had a set of tabs, one of which was the one I had a "ref=" in, and if I did not visit that tab then $refs was empty. But íf I first visited the tab then the ref was set up correctly.

Upvotes: 3

OdkoPP
OdkoPP

Reputation: 492

I had the same problem. I was using this.$refs in computed property. I just got rid of computed properties and used methods and everything started to work properly because $refs are not reactive as mentioned for example here and here

Upvotes: 4

George
George

Reputation: 6739

You can do it, the problem you have is that you don't actually have any refs on your parent component.

I do not recommend doing this anyway either use vuex, an eventbus or $emit

Vue.component('componentA', {
  template: '<div><span ref="ref-inside-a">You\'re in A!</span></div>',
  methods:{
  log(){
  console.log('Hello from a')
  }
  }
})

Vue.component('componentB', {
  props: ['ball'],
  template: '<div><span>This is B!</span></div>',
  mounted() {
    this.$root.$refs['a'].log()    
    console.log(this.$root.$refs['a'].$refs['ref-inside-a'])
  }
})

new Vue({
  el: "#app",
  mounted() {
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <component-a ref="a"></component-a>
  <component-b ref="b"></component-b>

</div>

Upvotes: 0

Related Questions