theberzi
theberzi

Reputation: 2685

How to get and observe ref to element from a v-for?

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 {}.

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

Upvotes: 0

Views: 326

Answers (1)

tony19
tony19

Reputation: 138556

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

Upvotes: 1

Related Questions