Home > front end >  How to get and observe ref to element from a v-for?
How to get and observe ref to element from a v-for?

Time:11-02

All answers that seem to tackle a similar issue relate to Vue 2 or simply don't give the expected results. Even what's described in the docs doesn't work, because if I log the value of the ref I just see an empty object.

I have a template like so:

<template>
  <post-content v-for="post in posts" :post-data="post" :key="post.id" ref="lastPost" />
</template>

The content of the PostContent component is unimportant, imagine it as a div displaying whatever's into post.content.

In the script I fetch posts from an API, and I'd like to have the last loaded post in the reference lastPost, so that I can access its HTMLElement (I need it for stuff, but here I just try to log it).

// This uses composition API with <script setup> syntax

const posts = ref<{id: number, content: string}[]>([])

getPostsFromApi()
  .then((thePost: {id: number, content: string}) => posts.value.push(thePost))

const lastPost = ref<typeof PostContent>()

watch(lastPost, () => nextTick(() => console.log(lastPost.value)), {flush: "post"})

However, this results in the log being a simple empty object {}.

  • I need to be able to access the HTMLElement itself once it has loaded.
  • I need to watch the reference for changes, so that I can get the element every time I add posts.
  • I only need the last post at any given time, I don't care about previous ones. I will only add posts one at a time. This is why I'm not using an array as a ref.

Why is an empty object being logged instead of what, according to the docs, should be expected? What am I doing wrong?

CodePudding user response:

Unlike SFCs with a regular <script> block, <script setup> components are closed by default -- i.e. variables inside the <script setup> scope are not exposed to the parent unless explicitly exposed via defineExpose(). An empty object in the logged template ref implies you haven't exposed any properties.

To expose the root element of PostContent, use a template ref in the component, and expose the ref (e.g., named "$el") with defineExpose():

// PostContent.vue
<script setup lang="ts">
const $el = ref()
defineExpose({ $el })
</script>

<template>
  <div ref="$el">...</div>
</template>

As an aside, watch() with { flush: 'post' } can be simplified to watchPostEffect():

watch(lastPost, () => nextTick(() => console.log(lastPost.value.$el)), {flush: "post"})

// OR more simply:
watchPostEffect(() => console.log(lastPost.value.$el))

demo

  • Related