Accessing $refs

Hello!

I’ve successfully used $refs before and am a little puzzled by this error

When I do a

console.log (this.$refs);

I see

{
  article: (with lots of stuff)
}

when when I go to

console.log(this.$refs.article)

it tells me its

undefined

Why’s that?

Thanks!

3 Likes

Where are you calling this?

You are logging the $refs object to the console before it contains any references (like, in created()). Then Vue continues, and adds the references to that object - so now they also appear in the console. You just can’t see this because it happened in a matter of milliseconds.

3 Likes

@james-brndwgn I did this in one of my single file components.

@LinusBorg If I understand correctly, your example would be valid if I logged the this.$refs.article first, but in my case, I did

console.log(this.$refs);

and saw the data that included this.$refs.article, but got the “undefined” after that when I

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

So I believe it should have already created that reference.

Well then where are you doing these console.logs, can you show us an example?

@LinusBorg is assuming you’re doing this in created and is correct in mentioning the side effects of what you see in the console.

Perhaps I wasn’t clear enough, in what lifecycle hook are you calling this? created(), mounted(), etc?

As mentioned by @LinusBorg if you call it in any hook before mounted() it will be undefined.

This is also a misunderstanding on their part as to why the console is behaving like this, and is a common gotcha to many people in javascript, printing a reference to an object at one point in time that shouldnt exist, but IS populated in the console when viewed.

And they are seeing this very side effect, as Linus mentioned.

3 Likes

Thanks for clarifying that @james-brndwgn

Hmm, this is my code … it was all in mounted()

mounted()
	{
		console.log( "Adding ref" );
		console.log( this.$refs );
		console.log( "Article" );
		let article = this.$refs.article;
		console.log( "printing this.$refs.article" );
		console.log( this.$refs.article );
		console.log( article );
	}

and both the last two console.log statements were “undefined”

tmp-vue-error

1 Like

And what about the template? Can you show us where and how in your template of your article ref?

This could also be a side effect of a delayed node, not being shown during say something like v-if being false, but then something triggering it later to be true.

Hmmm, well, at the point I added my $ref, there really was nothing in the template

<template>
  <d-nav>
    <div ref="article">
      <slot></slot>
    </div>
  </d-nav>
</template>

(How I’m using this is in my single.php file (it’s in WordPress))

<div is="single-page-component">
// stuff that into <slot>
</div>

There must be something we’re not seeing here for the bigger picture. Check my example fiddle

https://jsfiddle.net/kuukaj65/

Hmm, I saw your fiddle and it works. This is my whole component file:

<template>
  <d-nav>
    <div ref="article">
      <slot></slot>
    </div>
  </d-nav>
</template>

<style lang="scss">
  h1.single-title {
    font-size: 26px;
    font-weight: bold;
  }

  h2 {
    font-size: 22px;
  }

  h3 {
    font-size: 18px;
  }

  .centered {
    display: block;
    text-align: center;
  }
</style>

<script>
	

	export default {
		components: {
			"d-nav": () => import("./d-navigation.vue")
		},
		props: [],
		data()
		{
			return {}
		},
		methods: {},
		mounted()
		{
			console.log( "Adding ref" );
			console.log( this.$refs );
			console.log( "Article" );
			let article = this.$refs.article;
			console.log( "printing this.$refs.article" );
			console.log( this.$refs.article );
			console.log( article );
		}
	}
</script>

@bolerodan I changed your fiddle around a little, to add the ref in the template.

This reproduces the error I got.

https://jsfiddle.net/1fa3ca7j/

In this case though, printing this.$refs has no article

Sorry I dont see anything different in that fiddle?

I assume you meant to paste revision 1

https://jsfiddle.net/1fa3ca7j/1

But in this example, I still see everything being printed out fine into the console. So i’m not sure why you are not.

I would assume that it’s not working because he is using a lazy-loaded component:

components: {
  "d-nav": () => import("./d-navigation.vue")
}

When initially rendering that parent component, it will not wait for that async component (renders are always synchronous).

So The ref is empty after the first render. Then the lazy component has been loaded, and the parent re-renders - this time, the ref is added.

2 Likes

I understand what you’re saying @LinusBorg

However, if my initial

console.log(this.$refs);

shows

    {
      article: '//stuff'
    } 

as per my screenshot above, then shouldn’t the subsequent

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

be valid?

Unless the article ref gets removed somehow, between the first and second console.log()

This is what I (and @bolerodan) have been trying to explain to you, so you should read my initial explanation again:

I other words, the object you log to the console is empty initially, but is updated later, during the re-render, because its the same object that Vue updates in the component.

That’s why trying to access $refs.article doesn’t work - the object is empty at that moment, but a few milliseconds later, Vue has re-rendered and the article ref has been added. All of this happens much too quick for you to check manually.

1 Like

Here, a small demo of the behaviour with plain javascript:

https://jsfiddle.net/Linusborg/ddt2a15o/

I think I understand better … (kinda … because I did a few experiments that re-produced the situation on the

$nextTick()

When you said it was “empty initially”, I was countering that it wasn’t empty because it console.log()'ed the reference prior to the undefined. I didn’t think it was “empty” because it was “removed”.

But if it is “empty” because it was “removed” between my two console.log() statements, it makes a little more sense.

Though I did a brute force experiment …

mounted()
		{
			console.log( "Adding ref" );
			console.log( this.$refs );
			console.log( "Article" );
			let article = this.$refs.article;
			console.log( "printing this.$refs.article" );
			console.log( this.$refs.article );
			console.log( article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( this.$refs.article );
			console.log( "Should print by now" );
			this.$nextTick( function () {
						console.log( "Next tick" );
						console.log( this.$refs );
						console.log( "Next tick article" );
						console.log( this.$refs.article );
						return false;
					}
			)
		}

This results in:

Thanks for your patience @LinusBorg and @bolerodan I appreciate it! This is honestly so puzzling … unless it gets removed again on $nextTick … but it shouldn’t re-render with no new state changes.

Nothing was removed. This has to do with Javascript references… and primitive types and asynchonous code.

It also should be noted that the browser does NOT immediately console.log when you call it (it sorta waits when it has time to do so, a queue if you will). It all just happens so fast (as Linus mentioned) you dont see this.

Heres the thing. At that point in time, an object exists… this object is called the $refs object. When you print that out to the console, the browser has a refernece to it. At that point in time though, it is empty. The browser has NOT actually printed anything yet. Your next line, you send to the browser asking to print a key/value which still does not exist. It returns undefined… it is not a reference to an object, it is undefined. The browser STILL has not printed anything yet.

So what do we have at this point in time queued on our “stack” ? print a refernece to an object ($ref) and print a primitive (undefined) However, now the component has finished loading and has loaded your lazy loaded component… Now that object ($refs) has the key/value filled in article

And arbitrarily the browser has found time to actually send the content to the console to display. Since the object we’re printing is a reference, we see it filled in.

This is a common gotcha many people have with javascript, object references, and why the console behaves the way it does.

The console is not an accurate representation of “time” if thats how you want to look at it.

1 Like