80

I am trying to get $refs in Vue 3 using Composition API. This is my template that has two child components and I need to get reference to one child component instance:

<template>
    <comp-foo />
    <comp-bar ref="table"/>
</template>

In my code I use Template Refs: ref is a special attribute, that allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted.

If I use Options API then I don't have any problems:

  mounted() {
    console.log("Mounted - ok");
    console.log(this.$refs.table.temp());
  }

However, using Composition API I get error:

setup() {
    const that: any = getCurrentInstance();
    onMounted(() => {
      console.log("Mounted - ok");
      console.log(that.$refs.table.temp());//ERROR that.$refs is undefined
    });
    return {};
  }

Could anyone say how to do it using Composition API?

9 Answers 9

98

You need to create the ref const inside the setup then return it so it can be used in the html.

<template>
    <div ref="table"/>
</template>

import { ref, onMounted } from 'vue';

setup() {
    const table = ref(null);

    onMounted(() => {
      console.log(table.value);
    });

    return { table };
}
Sign up to request clarification or add additional context in comments.

9 Comments

The composition api work with setup function executing before on mounted function run, Therefore you wont have access to DOM, so creating new variable give you a way to create a handle to what will hold a dom when onMounted() function run. Am telling you man with composition api this is a trick around that problem
the variable you created on the html ref you wont have access to it on your setup() function since it run before onMounted() function have run. you only have access to it after a onMounted() function have executed,
Doesn't help. The table.value still comes out to be null for me.
@YashNag You probably need to create a new question so we can see your code.
@dantheman I made it work. It was a stupid oversight on my part. I was looking for this ref to be populated onMounted, but the component was dynamically loaded on certain conditions. Once i refactored the code for that, it worked just.
|
16

On Laravel Inertia:

<script setup>
import { ref, onMounted } from "vue";

// a list for testing
let items = [
  { id: 1, name: "item name 1" },
  { id: 2, name: "item name 2" },
  { id: 3, name: "item name 3" },
];

// this also works with a list of elements
let elements = ref(null);

// testing
onMounted(() => {
    
  let all = elements.value;
  let item1 = all[0];
  let item2 = all[1];
  let item3 = all[2];

  console.log([all, item1, item2, item3]);

});
</script>
<template>
  <div>

    <!-- elements -->
    <div v-for="(item, i) in items" ref="elements" :key="item.id">

      <!-- element's content -->
      <div>ID: {{ item.id }}</div>
      <div>Name: {{ item.name }}</div>

    </div>

  </div>
</template>

Comments

5
<template>
    <your-table ref="table"/>
    ...
</template>

<script>
import { ref, onMounted } from 'vue';

setup() {
    const table = ref(null);

    onMounted(() => {
      table.value.addEventListener('click', () => console.log("Event happened"))
    });

    return { table };
}
</script>

Inside your other component you can interact with events you already registered on onMounted life cycle hook as with my example i've registered only one evnet

2 Comments

Yes,i've considered that and i've responded on my code with the lines below comment which read as <!-- passing as reference to other component -->
If i get you well is, you dont want that line on setup function where we say const table = ref(null); as is creating new variable right? @Pavel_K
5

If you want, you can use getCurrentInstance() in the parent component like this code:

<template>
    <MyCompo ref="table"></MyCompo>
</template>

<script>
import MyCompo from "@/components/MyCompo.vue"
import { ref, onMounted, getCurrentInstance } from 'vue'
export default {
    components : {
        MyCompo
    },
    
    setup(props, ctx) {
        
        onMounted(() => {
          getCurrentInstance().ctx.$refs.table.tempMethod()
           
        });
    }
}
</script>

And this is the code of child component (here I called it MyCompo):

<template>
    <h1>this is MyCompo component</h1>
</template>

<script>
export default {
    
    setup(props, ctx) {
        
        const tempMethod = () => {
            console.log("temporary method");
        }

        return {
            tempMethod
        }
    },
}
</script>

3 Comments

getCurrentInstance() is technically "internal" code and as such its probably not a great idea to rely on it in prod code
@Graystripe I did not test it in prod codes, but because the actual question used getCurrentInstance(), I found a solution that works with that.
Prod code as in, production code -- yes, it will work now, but it isn't officially "supported" and might break without warning in the future
2

For me, the ref variable was not binding to the component because it was not yet rendered. I'll be extending @dantheman's solution. Consider the following:

<template>
    <div v-if="false">
        <div ref="table"/>
    </div>
</template>

import { ref, onMounted } from 'vue';

setup() {
    const table = ref(null);

    onMounted(() => {
      console.log(table.value);
    });

    return { table };
}

In this scenario where the <div ref="table"/> is not rendered because of the condition, the const table remains null. Let's say if the false turns into true, the const table is then populated. This is also clearly stated here in the docs:

Note that you can only access the ref after the component is mounted. If you try to access input in a template expression, it will be null on the first render. This is because the element doesn't exist until after the first render!

So it's not just onMounted that you have to look for, it's also whether the component to which ref is attached, is actually mounted.

Comments

2

Maybe TypeScript is easier for this. This worked for me:

const table = ref<HTMLDivElement | null>(null);

2 Comments

Is the 'table' then referenced by using the const variable name as table? - What if other elements as this.$refs.somethingElse is used. Would one the declare a const somethingElse = ref(null); ?
@DirkSchumacher Yes, which is ok for a few single elements. Otherwise you could store refs in an array.
1

For using refs in an array, this earlier answer logs an empty refs array in Vue versions between 3.2.25 and 3.2.31 inclusive:

const {ref, onMounted} = Vue;

Vue.createApp({
  setup() {
    const items = [
      {id: 1, name: "item name 1"},
      {id: 2, name: "item name 2"},
      {id: 3, name: "item name 3"},
    ];
    const elements = ref([]);
    onMounted(() => {
      console.log(
        elements.value.map(el => el.textContent)
      );
    });
    return {elements, items};
  }
}).mount("#app");
<div id="app">
  <div v-for="(item, i) in items" ref="elements" :key="item.id">
    <div>ID: {{item.id}}</div>
    <div>Name: {{item.name}}</div>
  </div>
</div>

<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>

A workaround is replacing refs="elements" with :ref="el => elements[i] = el":

const {ref, onMounted} = Vue;

Vue.createApp({
  setup() {
    const items = [
      {id: 1, name: "item name 1"},
      {id: 2, name: "item name 2"},
      {id: 3, name: "item name 3"},
    ];
    const elements = ref([]);
    onMounted(() => {
      console.log(
        elements.value.map(el => el.textContent)
      );
    });
    return {elements, items};
  }
}).mount("#app");
<div id="app">
  <div v-for="(item, i) in items"
       :ref="el => elements[i] = el"
       :key="item.id">
    <div>ID: {{item.id}}</div>
    <div>Name: {{item.name}}</div>
  </div>
</div>

<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>

1 Comment

This is fine for JavaScript, but if you are using Typescript typing doesn't work. I have an answer that should work here for typescript, and it would also work for javascript without the cumbersome on mounted in the setup.
1

Using useTemplateRef since Vue 3.5+ is the recommended way from the current official doc to obtain the reference with Composition API.

1 Comment

-1

Using the TypesScript compatible defineComponent, you can pass the desired code off to a method that does indeed have this.$refs defined:

export default defineComponent({
  name: 'ModeBar',
  methods: {
    registerToolTips(): boolean {
      //this.$refs is available!!!
    },
  },
  mounted() {
    this.$nextTick(() => this.registerToolTips());
  },
});

The this.$nextTick() may not be always necessary, and may not be enough sometimes. There is no guarantee that everything you want is rendered on mounted. You may have to put an interval on mounted if the intended $refs are not yet rendered, and then turn it off when they are found;

This is why the registerToolTips returns a boolean, so that if it doesn't work I could try it again.

2 Comments

In the example used, the syntax used is options API syntax, so this is not a solution to the question. Furthermore, this.$refs has always been available in the options API, so mentioning 'TypeScript' and 'defineComponent' is adding more confusion...
Some of us use typescript and defineComponent but still need an answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.