Using Vue.js's ref within a v-for iteration

When attempting to utilize components within a v-for loop and initialize the ref for future access to their methods from the parent component, I encountered an issue. Below is a simplified version of the code that demonstrates my scenario:

<template>
    <div class="hello">
        {{ msg }}
        <ul>
            <list-item 
                v-for="item in items" 
                :key="item.id" 
                :value="item.text" 
                :ref="`item${item.id}`"
            />
        </ul>
    </div>
</template>

<script>
    import ListItem from "./ListItem";
    export default {
        name: "HelloWorld",
        components: {
            ListItem
        },
        data() {
            return {
                msg: "Welcome to Your Vue.js App",
                items: [
                    { id: 1, text: "foo" },
                    { id: 2, text: "bar" },
                    { id: 3, text: "baz" },
                    { id: 4, text: "foobar" }
                ]
            };
        },
        mounted() {
            setTimeout(() => this.$refs.item2.highlight(), 1500);
        }
    };
</script>

The ListItem component looks like this:

<template>
    <li v-bind:class="{ highlight: isHighlighted }">
        {{value}}
    </li>
</template>

<script>
    export default {
        name: "list-item",
        props: ["value"],
        data() {
            return {
                isHighlighted: false
            };
        },
        methods: {
            highlight() {
                this.isHighlighted = !this.isHighlighted;
            }
        }
    };
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
    .highlight {
        color: red;
    }
</style>

The initial attempt resulted in an error stating:

Uncaught TypeError: _this.$refs.item2.highlight is not a function
. After further investigation, I discovered that refs defined within a v-for loop are not direct components but arrays containing the single component.
What is the rationale behind this behavior? How does it relate to the 'f' wrapper? Has anyone else encountered this issue and can provide an explanation or solution?
Although using
setTimeout(() => this.$refs.item2[0].highlight(), 1500);
worked, must one always pass [0]? Is there a more efficient approach available? Any assistance would be greatly appreciated.

Answer №1

When utilizing refs in conjunction with v-for, the component / DOM nodes are directly stored as an array under the variable name, eliminating the need to include index numbers in the ref names. This allows for a more streamlined approach like so:

<list-item
  v-for="item in items" 
  :key="item.id" 
  :value="item.text" 
  ref="items"
/>

You can then access the refs within your component using the following syntax:

this.$refs.items[index]

It is important to note that the order of the refs may not always align with the original sequence and should be managed differently if needed. For further details on this issue, refer to: https://github.com/vuejs/vue/issues/4952

Answer №2

Attention Vue 3 Users:

In Vue 3, the previous method will no longer automatically generate an array in $refs. To access multiple refs from a single binding, bind ref to a function for enhanced flexibility (a new feature):

HTML

<div v-for="item in list" :ref="setItemRef"></div>

Using Options API:

export default {
  data() {
    return {
      itemRefs: []
    }
  },
  methods: {
    setItemRef(el) {
      if (el) {
        this.itemRefs.push(el)
      }
    }
  },
  beforeUpdate() {
    this.itemRefs = []
  },
  updated() {
    console.log(this.itemRefs)
  }
}

Using Composition API:

import { onBeforeUpdate, onUpdated } from 'vue'

export default {
  setup() {
    let itemRefs = []
    const setItemRef = el => {
      if (el) {
        itemRefs.push(el)
      }
    }
    onBeforeUpdate(() => {
      itemRefs = []
    })
    onUpdated(() => {
      console.log(itemRefs)
    })
    return {
      setItemRef
    }
  }
}

For further information, visit the documentation link: https://v3-migration.vuejs.org/breaking-changes/array-refs.html

Answer №3

Managing refs within a v-for loop can be tricky, as the indexes of refs are not ordered:

<div v-for="(item, index) in items" @click="toggle(index)">
  <p ref="someRef"></p>
</div>

toggle(index) {
  this.refs['someRef'][index].toggle();
}

To solve this issue, I added a data attribute to each ref element:

<div v-for="(item, index) in items" @click="toggle(index)">
  <p ref="someRef" :data-key="index"></p>
</div>

Now each ref has a specific data-key and can be toggled using the following method:

toggle(index) {
  const dropdown = this.$refs['someRef'].find(
        el => el.$attrs['data-key'] === index
    );
  dropdown.toggle();
}

Answer №4

I encountered a similar issue.

Just like sobolevon pointed out, when using $refs.{ref name} in a v-for loop, the returning value is an array. To work around this, I treated $refs.{ref name} as an array with only one item by default and called the method using

$refs.{ref name}[0].methodToCall()
.

Fortunately, this approach resolved the issue for me.

Answer №5

Expanding on the solution provided by @Syed and incorporating Vue 3, the issue discussed can be found here: https://vuejs.org:

It's important to note that the order of elements in the ref array may not match the source array.

I encountered a situation where I needed the rendered list to align with the refs list. Here's how I addressed this problem:

<script setup>
  import { ref } from 'vue'
  import Comp from './Comp.vue'

  const list = ref([
    {
      name: 'Stripe',
      ref: null,
    },
    {
      name: 'Default',
      ref: null,
    }
  ]);

  function setItemRef(el, idx) {
    if (el) {
      list.value[idx].ref = el;
    }
  }
</script>

<template>
  <ul>
    <li v-for="(item, idx) in list">
      <Comp :ref="(el) => setItemRef(el, idx)"/>
      {{item}}
    </li>
  </ul>
</template>

You can see this example executed in SFC format here: https://sfc.vuejs.org

Answer №6

If you are working with Vue 3 and Typescript, you may encounter the issue discussed in this thread (vuejs/core#5525). Despite the ongoing status of the problem, there are potential workarounds available as mentioned by other users:

Update: The good news is that vuejs/core#5525 appears to have been resolved, so it might be worth exploring alternative solutions.

<div
   v-for="item in items"
   :ref="addRef"
   ...
</div>

...

function addRef(el: unknown) {
  if (el instanceof Element) {
    participantRefs.value.push(el);
  }
}

Answer №7

To solve the arrangement problem, I implemented a solution using a dynamic reference: :ref="'myRef' + index".

By following this approach, Vue generates a separate array for each item in the v-for loop, with the only element being the desired reference. This allows you to easily retrieve it using this.$refs['myRef' + index][0].

(Please note that this method is not compatible with Vue 3.)

Answer №8

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.21/vue.js"></script>
<div 
   v-for="(item,index) in items"
   :key="index">
<q-popup-proxy
   ref="qDateProxy">
  <q-date
     :data-key="index"
     v-model="item.date"
     @input="CalendarHide(index)"
     mask="MM/DD/YYYY"
    range>
  </q-date>
</q-popup-proxy>
</div>

<script>
function hideCalendar(Val) {
      this.$refs['qDateProxy'][val].hide();
}
</script>

Answer №9

If you have integrated `vueuse` into your project (which I highly suggest), there is a fantastic composable function available to help with this task, but it is only compatible with Vue 3.

<script setup>
import { watchEffect } from 'vue'
import { useTmplateRefsList } from '@vueuse/core'

const refs = useTemplateRefsList()

watchEffect(() => {
  if(refs.value.length > 0) {
    console.log(refs.value)
  }
})
</script

<tempalte>
  <ul>
    <list-item 
      v-for="item in items" 
      :key="item.id" 
      :value="item.text" 
      :ref="refs.set"
    />
  </ul>
</template>

For more information, visit: vueuse

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Executing a nested function before moving on to the subsequent code statements

I have a requirement where certain functions in my codebase need to check if a user is logged in before proceeding. Instead of duplicating this check logic, I want to call a single getUser() function each time. Here is the order of operations for the func ...

Using Selenium WebDriver and JavaScript: Enabling Chrome to Download Multiple Files at Once

After scouring through multiple documents for hours like https://www.selenium.dev/documentation/en/webdriver/js_alerts_prompts_and_confirmations/ as well as https://chromedriver.chromium.org/capabilities and I was unsuccessful in finding a solution wit ...

Unveiling the approach to accessing a nested function with jQuery

While the title may be a bit misleading, I couldn't think of a better way to describe it. I've created a function that allows a small pop-up window to appear when a link is clicked (to confirm whether or not an article should be deleted). Addit ...

What is the most efficient method for retrieving data upon form submission in Next.js?

When using the provided code and Metadata-scraper to retrieve meta data from a URL, I can do so successfully when it's hardcoded. However, I'm looking for guidance on how to allow users to input a link via a text field and fetch the meta data upo ...

The issue of broken reactivity arises when utilizing defineStore in Pinia with options instead of storeSetup

In my current project, I've implemented two different types of Pinia storage definitions. Here's a condensed look at each: // First Storage Definition using storeSetup export const useStore = defineStore("storeId", () => { const isExpanded: ...

The result of Document.getElementById can show as "undefined" despite the presence of the element

Currently, I am tackling a project that involves extracting information from a website. I have opted to use the 'puppeteer' library in Node.Js for this task. However, I am encountering an issue where Document.getElementById is returning "undefine ...

Problem with escaping special characters in random string HTML

As I was in the process of creating a JavaScript tool to generate random strings, with or without special characters, I stumbled upon an inspiring snippet that caught my attention: (): function randStr(len) { let s = ''; while (len--) s += ...

Issue encountered while attempting to remove a row from a table (JavaScript)

I'm encountering an error when attempting to delete a table row: "Uncaught ReferenceError: remTable is not defined index.html:1:1". When I inspect index.html to identify the issue, I find this: remTable(this) This is my code: const transact ...

I am having issues with this knob not updating, and the reason for this problem is unknown to me

Within my PHP code, I am utilizing the following: <?php @include_once('fields.php'); $gg = fetchinfo("val","inf","n","current"); $mm = fetchinfo("val","info","n","max"); $cc = fetchinfo("num","games","id",$gg); $percent = $cc / $mm * 100; ...

Determining when all textures have successfully loaded in Three.js and ensuring there are no lingering black rectangles

I'm currently developing a web platform that allows users to customize and preview 3D house models. If the user's browser doesn't support WebGL, the server renders the house and sends screenshots to the client. However, if the screenshots ar ...

Adding data to each span and div using JavaScript is a simple task that can be achieved easily

What is the best way to add information to each span and div element using JavaScript? $(document).on("click",".selection-state",function(){ stateid = $(this).attr("rel"); $("#my_tooltip").html(data); } e ...

Steps for creating a TypeScript project for exporting purposes

Forgive me for my lack of experience in the js ecosystem. Transitioning from static languages to typescript has been a positive change, though I still find myself struggling to grasp the packaging/module system, especially when coupled with typescript defi ...

Combining two ng-model inputs in Angular for seamless data integration

New to Angular and seeking some guidance. I currently have two input fields, one for the area code and the other for the number. // Input field for area code <input area-input type="tel" required="true" name="area" ng-model="employee.home.area">&l ...

Avoiding the selection of HTML canvas objects

I am currently working on customizing my homepage with an interactive animation. However, I am facing some challenges in integrating it seamlessly into the page. You can view the progress at . My main issue is preventing the canvas object from being select ...

What is the process for including a task in the current method?

I've been working on building a web app similar to Google Calendar. I have successfully created the necessary objects and methods, but now I need to implement a feature that allows users to add tasks. My current idea is for users to input a task which ...

Iterate through HTML content and utilize JavaScript along with Regular Expressions to substitute specific strings

In my HTML located in Anki, I have the following structure: <p>[!Quote] Title of callout 1<br>Content of callout 1</p> <p>[!Quote] Title of callout 2<br>Content of callout 2</p> <p>[!Quote] Title of callout 3<br ...

Is there a way to nest arrays within arrays in JavaScript?

Array ( [0] => Array ( [contactId] => 5 [companyId] => 54 [personName] => Awais [contactNo] => 0321-1111111 [contactType] => Partner ) ) data[0].personName I ...

What is the best way to deactivate a button when not all inputs require filling?

I need to make a change in my form where I want to disable a button, but not all the inputs are mandatory. Even though I have specified which inputs need to be filled in the code, I still have to fill all the inputs in the form. How can I modify this? $ ...

Tips for adjusting the alignment of the Vuetify component "VDatePicker" based on the position of its parent component on the screen

Currently, I am utilizing the VMenu component from Vuetify which contains another Vuetify component called VDatePicker. The issue arises when clicking on a text field triggers the appearance of the calendar (VDatePicker). Normally, the VDatePicker componen ...

Combining Files in Angular 2 for Optimal Production Deployment

What is the best method for packaging and combining an Angular 2 application for production? My goal is to have an index.html file and a single app.min.js file. Although the Angular 2 documentation mentions using webpack, I found it to be overly complex f ...