I'm working with Vue 3 and Nuxt 3 using the Composition API and the <script setup>
syntax. I've encountered an issue where I'm unable to access the DOM elements of my custom component when using template refs with v-for
. Contrastingly, the approach works as expected with a basic <li>
element. The focus of my issue revolves around the peculiar behaviour of template refs when combined with v-for
in this context.
Here's a reduced version of my code:
Template:
<template>
<main>
<section v-if="projectData && projectData.projects" ref="projectSection">
<li v-for="item in listItems" ref="listItemRef">
{{ item }}
</li>
<div ref="sectionContainer">
<ProjectCard
v-for="(project, index) in projectData.projects"
ref="projectElementRefs"
:key="index"
:project="project"
@click="handleClick(project, index)"
/>
</div>
</section>
</main>
</template>
Script (using <script setup>
):
const projectData = await fetchData({
query: homeQuery,
});
const listItemRef = ref(null);
const listItems = ref(['item1', 'item2', 'item3']);
const projectElementRefs = ref([]);
const handleClick = (project, index) => {
console.log(projectElementRefs.value); // Proxy Array of 10 in length
console.log(projectElementRefs.value[0]); // Proxy object, not the value itself
console.log(projectElementRefs.value[0].value); // undefined
console.log(listItemRef.value); // Proxy Array of 3 in length
console.log(listItemRef.value[0]); // <li> element
}
For clarity, this click event triggers after the component has mounted. The console.log
statements return the following:
console.log(projectElementRefs.value)
- Proxy Array with 10 items.console.log(projectElementRefs.value[0])
- Proxy object, but not the DOM element.console.log(projectElementRefs.value[0].value)
- undefined
.console.log(listItemRef.value)
- Proxy Array of 3 items.console.log(listItemRef.value[0])
- The actual <li>
element.Has anyone encountered a similar challenge? In the docs, it says it should be populated with the elements after mounting.
https://vuejs.org/guide/essentials/template-refs.html
I would be so grateful if anyone knows how to access the element from my template ref array from v-for.
From the Vue documentation
An exception here is that components using are private by default: a parent component referencing a child component using
won't be able to access anything unless the child component chooses to expose a public interface using the defineExpose macro:
I'll assume that this are the basic codes inside your ProjectCard
component
<script lang="ts" setup>
const props = defineProps<{
project: string
}>()
const projectRef = ref<HTMLElement | null>(null)
👇🏽 // Don't forget this.
defineExpose({
projectRef
})
</script>
<template>
<div ref="projectRef">
{{ project }}
</div>
</template>
<style scoped lang="css"></style>
Now, in your parent component or page.
<script setup>
const { data: projects } = await useFetch( '/api/projects' )
const projectElementRef = ref( null )
function onHandleProject( index ) {
console.log( 'Clicked project ', projectElementRef.value[ index ].$refs.projectRef.innerText )
}
</script>
<template>
<div>
<ProjectCard
v-for="(project, index) in projects"
:key="project"
:project="project"
ref="projectElementRef"
@click="onHandleProject(index)"
/>
</div>
</template>
<style scoped lang="css"></style>
While testing, the expected output will be like this.
If you want to access the HTML elements
function onHandleProject( index ) {
console.log( 'Clicked project ', projectElementRef.value[ index ].$refs.projectRef )
}
The expected output will be this.
The projects API example.
~/server/api/projects.get.ts
export default defineEventHandler((event) => {
const projects = []
for (let x = 0; x < 10; x++) {
projects.push(`Project num ${x}`)
}
return projects
})
Hope that helps.