Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

dcranmer's avatar

focus on template ref in for loop (Composition API) (Solved)

I don't understand how to work with template refs in the Composition API. I need to move the focus to a single list item when a "Show more" button is clicked. I'm trying to use template refs in a simple method, but keep getting an error TypeError: Cannot read properties of undefined (reading 'value'). But I can log myRef.value to the console in the same method.

yes, c2 [object Object]

Since this is inside a v-for loop, I define my ref in <script setup> like this:

let c2 = ref([])

All the method does is toggle showAllListItems to show the heretofore hidden list items, and move the focus:

const showFullList =  () => {
  if (c2) {
    console.log('yes, c2 ' +c2)
  }
 
  showAllListItems.value = !showAllListItems.value

   c2[0].value.focus()
   }

I've tried nextTick(), but that doesn't help. What is the secret for moving focus to a template ref in the Composition API?

0 likes
7 replies
LaryAI's avatar
Level 58

The issue is that c2 is defined as an empty array, so c2[0] is undefined. To fix this, you need to define c2 as a ref to an empty array, like this:

let c2 = ref([]);

Then, in your template, you can use the ref directive to bind the ref to the list item:

<ul>
  <li v-for="item in items" :key="item.id" ref="listItem">{{ item.name }}</li>
</ul>

Finally, in your method, you can access the ref using $refs and move the focus to the first list item like this:

const showFullList = () => {
  showAllListItems.value = !showAllListItems.value;
  const listItem = $refs.listItem[0];
  if (listItem) {
    listItem.focus();
  }
};
dcranmer's avatar

Sorry Lary, this doesn't work. Any humans out there that would be good enough to assist?

dcranmer's avatar

@dacfabre @martinbean

	ok, although I've altered a few things. The name of ref to `listItem`, and am now trying to target listItem[5]. 

const listItem = ref([])

My v-for:

              <li
                v-for="(county, index) in project.data.counties"
                v-show="showornot(index)"
                class="project-counties-list"
                ref="listItem"
                :key="county.id"
              >
                {{ county.county_name }}, {{ index }}
              </li>

In the button method for moving the focus, i've tried 1) testing for listItem[5], in which case it doesn't find it; or 2) moving the focus via listItem[5].value.focus(), which returns an error.

const showFullList = () => {
  if (listItem) {
    console.dir(listItem)
  } else {
    console.log('nope')
  }

  document.activeElement.blur()
  showAllCounties.value = !showAllCounties.value
  
  // const target = listItem[5]
  // if (target) {
  //   target.value.focus()
  //   console.log('yes, ' +target)
  //  } else {
  //   console.log('no')
  // }
  listItem[5].value.focus()

}

But I can log both listItem (below) and listem[5] to the console:

RefImpl
dep
: 
undefined
__v_isRef
: 
true
__v_isShallow
: 
false
_rawValue
: 
(12) [li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list, li.project-counties-list]
_value
: 
Proxy(Array) {0: li.project-counties-list, 1: li.project-counties-list, 2: li.project-counties-list, 3: li.project-counties-list, 4: li.project-counties-list, 5: li.project-counties-list, 6: li.project-counties-list, 7: li.project-counties-list, 8: li.project-counties-list, 9: li.project-counties-list, 10: li.project-counties-list, 11: li.project-counties-list}
value
: 
(...)
[[Prototype]]
: 
Object

From what I've read, there's something going on about the refs not rendering yet (or something like that), but it's not clear to me how to deal with it in a case like this.

showornot() just shows and hides list items based on index

dcranmer's avatar

@martinbean I've also tried using listItem.value[5], which may be correct vs.listItem[5].value. I've tried logging it to the console in onMount() and using it to move focus; e.g., listItem.value[5].focus(). But that also returns an error.

dcranmer's avatar
dcranmer
OP
Best Answer
Level 6

Ok, I finally solved this. It only took a few hours of banging my head against the wall. The two keys were to use listItem.value[5] (and not listItem[5].value) and nextTick(). I had also forgotten to include the tabindex in my previous example. So:

<script setup>
import { ref, nextTick } from 'vue'
const listItem = ref([])

let showAllCounties = ref(false)

const showHideItems = (index) => {
  return showAllCounties.value
  ? true
  : index < 5
}

const showHideFullList = () => {
  showAllCounties.value = !showAllCounties.value
  nextTick(() => {
    const target = listItem.value[5]
    if (target) {
      target.focus()
    }
  })
}

const showMoreButtonText = computed(() => {
  return showAllCounties.value
    ? 'Show less'
    : 'Show more'
})
</script>

<template>
<ul>
<li
  v-for="(county, index) in project.data.counties"
  v-show="showHideItems(index)"
  :id="`li-${index}`"
  class="project-counties-list"
  ref="listItem"
  :key="county.id"
  :tabindex="index === 5 ? -1 : false"
>
  {{ county.county_name }}
</li>
 </ul>
  <button
	type="button"
	class="toggle"
    @click="showHideFullList"
  >{{ showMoreButtonText }}
  </button>
</template>

Please or to participate in this conversation.