Guide to accessing references in a nested v-for loop using Vue 3, Typescript, and the Composition API

I've been grappling with the challenge of migrating Vue2 to Vue3 Composition API using Typescript. Refs don't seem to function in the same way, leaving me unsure of how to make them work in Vue3. It used to be so simple in Vue 2 with a code snippet like

this.$refs[docForm.value.fields[i].name][0].focus()

The approach outlined in the Docs where you just add the ref on the v-for loop and access it by index doesn't quite fit my scenario. The issue is that I only have the field name at hand when I need to access it, not the index position (it's a long story).

<div v-for="(p, i) in docForm.pages">
    <div v-for="(f, i) in p.fields">
        <input type="text" :name="f.name" :ref="f.name" />
    </div>
</div>
const goToNextEmptyField = (): void => {
    if (docForm.value) {
        for (var i = 0; i < docForm.value.fields.length; i++) {
            if (docForm.value.fields[i].value == null || docForm.value.fields[i].value == '') {
               refs[docForm.value.fields[i].name][0].focus() //THIS LINE NEEDS FIXING - WORKED IN Vue2
               return
            }
        }
    }
}

This is the simplified structure of the docForm object:

export interface DocForm {
    pages: DocFormPageModel[]
    fields: DocFieldModel[]
}

export interface DocFormPageModel {
    fields: DocFormField[]
    ...
}

export interface DocFieldModel {
    name: string
    ....
}

Answer №1

To work with Vue 3, it is necessary to declare refs.

Here's a suggestion:

import { ref } from 'vue'

const inputs = ref([]);
// For TypeScript implementation
// const inputs : Ref<HTML element[]> = ref([]);

const goToNextEmptyField = (): void => {
    if (docForm.value) {
        for (var i = 0; i < docForm.value.fields.length; i++) {
            if (docForm.value.fields[i].value == null || docForm.value.fields[i].value == '') {
               inputs.value[docForm.value.fields[i].name][0].focus()
               return
            }
        }
    }
}

This code initializes a reactive object that gets filled when the refs are identified.

Remember to always access a ref using its value property in the script (not in the template).

You can find more information about this in the Vue documentation. https://vuejs.org/guide/essentials/template-refs.html#refs-inside-v-for

Don't forget to enable the "Composition API" toggle switch!

Answer №2

Danial Storey guided me in the right direction and here is the functional solution I came up with:

<script setup lang="ts">
import { ref, onMounted } from 'vue'
  
interface DocForm {
    pages: DocFormPageModel[]
    fields: DocFieldModel[]
}

interface DocFormPageModel {
    fields: DocFormField[]
}

interface DocFieldModel {
    name: string
    value: string
}

const docForm = ref<DocForm>(
  {pages: [
    { fields: [
      { name: "test1", value: "1", masterFieldIndex: 0 },
      { name: "test2", value: "", masterFieldIndex: 1 },
      { name: "test3", value: "", masterFieldIndex: 2 },
    ]}
  ], fields: [
    { name: "test1", value: "1", masterFieldIndex: 0 },
    { name: "test2", value: "", masterFieldIndex: 1 },
    { name: "test3", value: "", masterFieldIndex: 2 },
  ]}
)

const pageRefs = ref<HtmlElement[]>([])
const fieldRefs = ref<HtmlElement[]>([])

const goToNextEmptyField = (): void => {
    if (docForm.value) {
        for (var i = 0; i < docForm.value.fields.length; i++) {
            if (docForm.value.fields[i].value == null || docForm.value.fields[i].value == '') {
               var inputToFocus = fieldRefs.value.filter((f)=>{ 
                   return f.children[0].name == docForm.value.fields[i].name
               })
               inputToFocus[0].children[docForm.value.fields[i].name].focus()
               return
            }
        }
    }
}
</script>

<template>
    <div v-for="p in docForm.pages">
      <div v-for="f in p.fields" ref="fieldRefs">
        <input type="text" v-model="docForm.fields[f.masterFieldIndex].value" :name="f.name" />
      </div>
    </div><br />
  <button @click="goToNextEmptyField">
    Go to on First Empty Field
  </button>
</template>

Feel free to test out the code here.

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

What is the best way to implement dotenv in a TypeScript project?

Attempting to load .env environment variables using Typescript. Here are my .env and app.ts files: //.env DB_URL=mongodb://127.0.0.1:27017/test // app.ts import * as dotenv from 'dotenv'; import express from 'express'; import mongoo ...

Vue.js $scopedSlots do not function with Vue object

In the process of developing a Vue component that will be released once completed, I am wrapping Clusterize.js (note that the vue-clusterize component is only compatible with v1.x). The goal is to efficiently render a large list of items using Vue, particu ...

Are you utilizing NuxtJS (or VueJS) for managing dynamic Django forms?

My Django website is styled with Bootstrap, and its main purpose is to display a form, submit the data to a Django View, and render an output page based on the form data. The form is quite dynamic, incorporating jQuery to enable or disable certain fields b ...

Having trouble with importing rxjs operators

After updating the imports for rxjs operators in my project to follow the new recommended syntax, I encountered an issue with the "do" operator. While switchMap and debounceTime were updated successfully like this: import { switchMap, debounceTime } ...

Accessing Child Properties in Parent Component using Typescript

New to the world of Typescript! Imagine having a component called TitleSubtitle that consists of both a Title and a Subtitle component. The Title component comes with props: interface TitleProps { text: string; } The Subtitle component also has props ...

Learn how to extract JSON information from a URL and integrate it into either a table or dropdown in Vue.js

Looking to retrieve a JSON data array from a URL and utilize it to populate either a table or dropdown with Vue.js and Axios. Here is the link where I intend to fetch the data. Any advice on how to accomplish this? https://jsonplaceholder.typicode.com/us ...

Turning Elasticsearch JSON data into interactive links using Vue.js

Hello everyone! I'm currently working on setting up a search engine using elasticsearch, node.js, express, and vue.js. My goal is to have the search results display as clickable links, however, they are only showing as non-clickable HTML text. I' ...

Having difficulty in synchronizing the redux state and realm database into harmony

Struggling to update my redux store with data from useState. While troubleshooting, I noticed that errors are often related to the realm database, impacting the redux store unintentionally. LOG [Error: Wrong transactional state (no active transaction, wr ...

IntelliJ does not support the use of newlines within Vue.js component templates

While working with Vue.js in IntelliJ IDEA, I encountered a small problem related to defining component templates. The issue is that IntelliJ seems to struggle when the template spans more than one line and attempts to concatenate them together. For examp ...

Adding images in real-time

I am currently working on an Angular application where I need to assign unique images to each button. Here is the HTML code snippet: <div *ngFor="let item of myItems"> <button class="custom-button"><img src="../../assets/img/flower.png ...

What causes tests to fail with an error message SyntaxError: Unexpected token {?

Hey there! I'm encountering an issue with webpack 5 and babel while trying to run tests that were previously written. Despite following the jest documentation for configuration, I haven't been able to find a solution after exploring various forum ...

Retrieving data from the setlist FM API

Greetings, I am currently working on a project using Vue and Bootstrap to create a grateful dead website. The idea is to allow users to search by year to display setlists, with a limit of 5 setlists per page that can be scrolled through. However, I have h ...

Access the 'from' path from Vue Router within a component

Is there a way to retrieve the previous route within a component without using router.beforeEach in router/index.js? I need to display different DOM elements depending on where the user navigated from to reach the current route. Although console.log(this ...

Array of notifications in Vue framework

I am facing an issue with returning an array of notifications from my backend. I have a simple wizard form that displays success messages using Toastification. Here is how it looks: this.$toast({ component: ToastificationContent ...

Attaching a button to a data model

I am working with checkboxes that are output from options data in a loop: <input type="checkbox" v-model="option.active"> options: [ { name: 'one', active: false, }, { name: 'tw ...

A guide on customizing the appearance of individual items in a vue v-for loop based on specific conditions

I am currently developing a multiple choice quiz game and I want the selected answer by the user to change color, either red or green, depending on its correctness. To achieve this, I have created a variable called selected that correctly updates when the ...

Expanding ngFor in Angular 2

Is it possible to pass two arguments with ngFor? Here is an example that I would like to achieve: <mat-card *ngFor="let room of arr; let floor of floorArr"> <mat-card-content> <h3>Room Number: {{room}}</h3> <p>Floor ...

What are some alternatives to using multiple slot transclution in an Angular 1.5 component?

In the process of constructing a panel component using angular 1.5, I am looking to embed some markup into this template (which has been simplified): <div class="panel"> <h1>{{ $ctrl.title }}</h1> <div ng-transclu ...

Utilize the prototype feature from a versatile source

Can a class with a generic like class Foo<A> {} access A's prototype or use a typeguard on A, or perform any kind of logic based solely on A's type - without being given the class, interface, or instance to Foo's constructor (e.g. when ...

The specified type 'IterableIterator<number>' does not belong to either an array type or a string type

Encountering an error while attempting to generate a dynamic range of numbers. Error: Type 'IterableIterator<number>' is not recognized as an array or string type. Use the compiler option '--downlevelIteration' to enable iteratio ...