Search code examples
vue.jsnuxt.jsvuejs3nuxt3.js

Unable to access the element when using template refs with v-for in Vue3/Nuxt3 on a custom component


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.


Solution

  • 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. enter image description here

    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. enter image description here

    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.