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

Using an array input in a Vue.js v-for loop

Creating a form to collect information about multiple persons can be challenging. Let's say we have 3 people to gather info on, and we need to structure the data in JSON format like this: { persons[0].surname: '', persons[0].name: &apos ...

Modifying the height of the bar in Google Charts Timeline using react-google-charts

I am currently working on a Google Chart timeline using react-google-charts. <Chart chartType="Timeline" data={data} width="100%" options={{ allowHtml: true bar: { groupWidth: 10 }, }} ...

Discovering the value of an object through its prototypes

Is it possible to create a function that can locate the value "5" within an object's prototype? What is the proper algorithm to achieve this? var rex = { "Name": "rex", "Age": 16, } te = { "to": 5, } rex.te = Object.create(te); function findValu ...

Template does not reflect changes made to filters in real-time

I've been working on filtering my "PriceList" collection and sorting is functioning perfectly. However, I'm experiencing some issues with implementing filters and search functionality. When I click on custom filter buttons, the template doesn&apo ...

Is there a way to streamline the process of connecting multiple ajax requests automatically?

After reviewing the lower portion of my function, I realized that I need to repeat info(url_part1 + next + url_part2, function(next) { multiple times. Is there a more efficient way to accomplish this task, perhaps utilizing some type of loop? I have been b ...

Obtaining the current value with each keystroke

While working with vue.js, I'm building a table that contains an input field called quantity. However, when I start typing the first word, it shows 'empty' on the console. If I type 3, it displays empty; and if I type 44, it prints 4. I am ...

Smooth-scroll plugin does not activate active state (due to JS modification)

I'm currently facing an issue with a script that handles smooth scrolling and the active state on my main navigation. The plugin in question can be found at: It's important to note that the navigation bar is fixed and therefore has no height. T ...

Retrieve data from a JSON file

I have a JSON file containing various player data, and I need to extract the "Name" field from it. { "player": [ { "Position": "TEST", "Name": "TEST", "Squad_No": "TEST", "Club": "TEST", "Age": "TEST" }, ...

How to properly size a child div inside a parent container

I'm having trouble with sizing a child div inside a parent div. The problem is that the child div's size changes according to the number of elements it contains, but I want all the child divs to be the same size regardless. This issue arises with ...

Efficient PHP caching solution for optimizing JavaScript and CSS performance

I'm facing a unique challenge that I can't seem to solve through typical Google searches. I'm in the process of consolidating all my javascript and css into separate php files using require_once() to pull in the content. The structure of my ...

React - Uncaught Error: e.preventDefault is not a function due to Type Error

Encountering an issue with Axios post and react-hook-form: Unhandled Rejection (TypeError): e.preventDefault is not a function The error arises after adding onSubmit={handleSubmit(handleSubmitAxios)} to my <form>. Seeking to utilize react-hook-form ...

Using Node.js to render when a task has been completed

I am currently developing a Node.js Application using Express.js. One of the challenges I face is rendering data from another site on a page using Cheerio.js. While this in itself is not an issue, I struggle with determining how to render the data once the ...

Effortlessly create a seamless transition in background color opacity once the base image has finished

I've set up a div with a sleek black background. Upon page load, I trigger an API request for an image, which is then displayed in a secondary div positioned behind the main one. After this, I aim to smoothly transition the overlaying div's opaci ...

tagit: update the label's value

I've been utilizing the jquery ui plugin https://github.com/aehlke/tag-it to incorporate tagging functionality into my project. This is how I am creating tags: $("#Input").tagit("createTag", "ABC"); My goal is to append additional text to the labe ...

Tips for customizing the background color of the MUI Menu Popover within a TextField that has the select property

In my quest to customize the appearance of a popover or menu in a TextField with the 'select' property, I referred to MUI customization docs for guidance. Successfully changing the text and label color of a TextField using the code below: const u ...

Create a stylish navigation dropdown with MaterializeCSS

Incorporating the materializecss dropdown menu feature, I encountered an issue where only two out of four dropdown menu items were visible. Here is the HTML code snippet in question: <nav class="white blue-text"> <div class="navbar-wrapper con ...

Issue: Laravel not able to handle FormDataDescription: The Laravel backend seems to

This question pertains to Vue and Laravel. I'm attempting to make an API request in Vue using const response = await http.put("api/v1/vehicles/" + vehicleId,formData);. I can see the data being sent in the payload, but when I use dd($request ...

Transferring streaming data from Node.js to an ElasticSearch database

Currently, my Node.js script is extracting data from a large USPTO Patent XML file (approximately 100mb) to create a patentGrant object. This object includes details such as publication number, country, date, and type of patent. I am working on storing a ...

How can we determine the total character count of a file that has been loaded into a textarea

I have a textarea where I can count the number of characters as I type. function calculateCharacters(obj){ document.getElementById('numberCount').innerHTML = obj.value.length; } <textarea name="textField" id="my_textarea" class="text_edit ...

The coordinates of the event do not match the coordinates of the location. Successful AJAX response data

How can I retrieve the accurate latitude and longitude when the Google Maps marker finishes dragging? It's confusing because for the same exact point, two different (but very approximate) coordinates are provided. Here are some example results for t ...