Skip to content
Advertisement

How do I target all items in a list, when a change occurs in Vue.js?

I’m building a site that uses Vue for to power the majority of the UI. The main component is a list of videos that is updated whenever a certain URL pattern is matched.

The main (video-list) component looks largely like this:

let VideoList = Vue.component( 'video-list', {
    data: () => ({ singlePost: '' }),
    props: ['posts', 'categorySlug'],
    template: `
        <div>
            <transition-group tag="ul">
                <li v-for="(post, index) in filterPostsByCategory( posts )">
                    <div @click.prevent="showPost( post )">
                        <img :src="post.video_cover" />
                        /* ... */
                    </div>
                </li>
            </transition-group>
        </div>`,
    methods: {
        orderPostsInCategory: function ( inputArray, currentCategory ) {
            let outputArray = [];
            for (let i = 0; i < inputArray.length; i++) {
                let currentCategoryObj = inputArray[i].video_categories.find( (category) => {
                    return category.slug === currentCategory;
                });
                let positionInCategory = currentCategoryObj.category_post_order;
                outputArray[positionInCategory] = inputArray[i];
            }
            return outputArray;
        },
        filterPostsByCategory: function ( posts ) {
            let categorySlug = this.categorySlug,
                filteredPosts = posts.filter( (post) => {
                    return post.video_categories.some( (category) => {
                        return category.slug === categorySlug;
                    })
                });
            return this.orderPostsInCategory( filteredPosts, categorySlug );
        }
    }
});

The filterPostsByCategory() method does its job switching between the various possible categories, and instantly updating the list, according to the routes below:

let router = new VueRouter({
    mode: 'history',
    linkActiveClass: 'active',
    routes: [
        { path: '/', component: VideoList, props: {categorySlug: 'home-page'} },
        { path: '/category/:categorySlug', component: VideoList, props: true }
    ]
});

The difficulty I’m having is transitioning the list in the way that I’d like. Ideally, when new category is selected all currently visible list items would fade out and the new list items would then fade in. I’ve looked at the vue transitions documentation, but haven’t been able to get the effect I’m after.

The issue is that some items have more than one category, and when switching between these categories, those items are never affected by whatever transition I try to apply (I assume because Vue is just trying to be efficient and update as few nodes as possible). It’s also possible that two or more categories contain the exact same list items, and in these instances enter and leave methods don’t seem to fire at all.

So the question is, what would be a good way to ensure that I can target all current items (regardless of whether they’re still be visible after the route change) whenever the route patterns above are matched?

Advertisement

Answer

Have you noticed the special key attribute in the documentation?

Vue.js is really focused on performance, because of that, when you modify lists used with v-for, vue tries to update as few DOM nodes as possible. Sometimes it only updates text content of the nodes instead of removing the whole node and then append a newly created one. Using :key you tell vue that this node is specifically related to the given key/id, and you force vue to completely update the DOM when the list/array is modified and as a result the key is changed. In your case is appropriate to bind the key attribute to some info related to the post and the category filter itself, so that whenever the list is modified or the category is changed the whole list may be rerendered and thus apply the animation on all items:

<li v-for="(post, index) in filterPostsByCategory( posts )" :key="post.id + categorySlug">
    <div @click.prevent="showPost( post )">
        <img :src="post.video_cover" />
        /* ... */
    </div>
</li>
User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement