简体   繁体   中英

How to reapply eventlistener on a computed array of divs in Vue 3?

I would like to display an array of divs, with the goal of adding a class on the div itself and on a child element when hovering the div.

The array of divs are the result of a computed function. Here's the code:


 <article
    v-for="(article, index) in filteredArticles"
    :key="article.id"
    ref="articleRefs"
    :ref-key="index"
    @mouseenter="handleClass(index, 'add')"
    @mouseleave="handleClass(index, 'remove')"
>
    <!-- Content -->
</article
const articleRefs = ref<HTMLDivElement[]>([])
const articleList = ref([] as Article[])

const filteredArticles = computed(() => {
    return activeTag.value !== ''
        ? articleList.value.filter((a) => a.tags.some((t) => t.Tag_id.title === activeTag.value))
        : articleList.value
})

watch(
    filteredArticles.value,
    () => articleRefs.value = []
)

const handleClass = (index: number, action: 'add'|'remove') => {
    const hoveredArticle = articleRefs.value[index]
    hoveredArticle.classList[action]('animate')
    hoveredArticle.querySelector('h2')?.classList[action]('glitch')
}

When the page loads, everything works, and as long as I change the tags to display an article that didn't get filtered out, my handleClass methods works / gets called.

However, if an article filtered out gets displayed again, nothing happens on hovering.

What am I missing?

Thank you in advance

There is a problem, if you use the index of your array that loops through the articles and try to map it to the index of the refs. If you look into the vue documentation you will see, that the order of the items is not guaranteed the same as of your data array

It should be noted that the ref array does not guarantee the same order as the source array.

This will lead to the problem, that when using the index of the for loop, you might have a different index in your refs array.

Solution

The idea is to separate your data logic from the presentation logic with a new SingleArticle Vue Component (the naming might be different depending on your context, but it is suggested to have at least a two word component name.). If you build it as follows, the Vue framework will make sure, your update logic works and you don't need to care for that.

1. Single Article Component

  • the root class animated is applied from the framework depending on the hover state
  • As I understood from the context, the contents of the article are passed as HTML string, so you need to use querySelector to get the element inside the contents and apply a class to it.
<!-- vue sfc template: -->
<article
  ref="articleRef"
  :class="{ animated: isHover }"
  v-html="content"
  @mouseenter="isHover = true"
  @mouseleave="isHover = false"
/>
// vue sfc script (or script setup)
import { ref, defineComponent } from 'vue';

export default defineComponent({
  name: 'SingleArticle',
  props: {
    // what ever is in your article props
    // but I add some example props to showcase the idea
    content: String,
    // ...
  },
  setup(props) {
    var articleRef = ref<HTMLElement>(null);
    var isHover = ref<boolean>(false);

    watch(isHover, (currIsHover) => {
      const action = currIsHover ? 'add' : 'remove';
      articleRef.value?.querySelector('h2')?.classList[action]('glitch');
    });
    
    return {
      articleRef,
      isHover,
    }
  }
});

2. use the new component within v-for

<!-- inside the vue sfc template -->
<single-article
 v-for="(article, index) in filteredArticles"
 :key="article.id"
 :content="article.content"
/>

// the main component only contains the logic for the
// filtering and the hovering logic is completely done
// inside the new single article component.
export default defineComponent({
  setup() {
    const articleList = ref<Article[]>([]);

    const filteredArticles = computed(() => {
      if (!activeTag.value) return articleList.value;

      return articleList.value.filter(
        (a) => a.tags.some((t) => t.Tag_id.title === activeTag.value)
      );
    }
    
    return {
      filteredArticles,
    };
  }
})

final thoughts and remarks

I didn't test the implementation, this is just written down from what was in my mind.

The solution will separate the filter logic from the presentation logic of the article which will give your two clean components, that are easy to test and also easy to understand. The updates will work out of the box, as vue will cater reactively for it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM