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
CodePudding user response:
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"
:
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.