Jumping with transition group

I am using vue3 with standard html and in my template I have a product grid and an api endpoint. When I apply a product filter, it hits an endpoint that returns the updated collection like this:

const vm = Vue.createApp({
    data() {
      collection: null,
      loaded: false
    },
    mounted() {
       this.fetchCollection()
    },
    methods: {
      fetchCollection(){ 
          fetch('/example.com/collections/someCollection')
             .then(res => res.json())
             .then(data => {
                 this.loaded = true
                 this.collection = data
          })
     },
     getFilteredProducts(filter) { 
        fetch(`/example.com/collections/someCollection?${filter}`)
           .then(res => res.json())
           .then(data => {
              this.loaded = true
              this.collection = data
        })
      }
  }
}).mount('#app')

Now in my template I have some code and I have a <transition-group> element and some css for it.

<div v-if="!loaded" class="loading-wrapper">Loading...</div>
<div v-else class="grid-container">
     <div class="gridCategory_wrapper">
       <div class="grid-wrapper">
         
         <transition-group>
         <template v-for="(product, index) in collection.products" :key="product.id">

           <!-- Promotional Image -->
           <div v-if="index === 2" class="grid grid--2x2">
             <div class="gridCategory__item gridCategory__item--doubleRow">
               <div class="categoryEditorial">
                 <div class="card__imgWrapper">
                   <img class="card__img" src="//cdn.shopify.com/s/files/1/0252/8968/7128/files/fa_pic.jpg?v=8604365058591944420" alt="">
                 </div>
               </div>
             </div>
           </div>
           <!-- Regular product tile -->
           <div  class="grid grid--1x1">
             <div class="gridCategory__item">
               <div class="productQB productQB__alt">

                 <div class="productQB__wrapperOut">
                   <a :href=`/collections/${collection.handle}/products/${product.handle}`>
                     <div class="productQB__imageFront">
                       <img :src="product.featured_image" alt="">
                     </div>
                   </a>
                 </div>

                 <a href="">
                   <div class="productQB__info">
                     <div class="productQB__desc">
                       <div class="productQB__first-line">
                         <div class="productQB__title">{% raw %}{{ product.title }}{% endraw %}</div>
                       </div>
                       <div class="productQB__second-line">
                         <div class="productQB__infoPrice">
                           <p class="productQB__price">{% raw %}{{ formatMoney(product.price) }}{% endraw%}</p>
                         </div>
                         <div class="color-variant">
                           <div class="color-variant-count-label"><span>3</span> Colors</div>
                         </div>
                       </div>
                     </div>
                   </div>
                 </a>

                 <!-- here needs if logic and then use the product tag object -->
                 <div class="productQB__metaLabel">
                   <p class="productQB__metaText">New Collection</p>
                 </div>

               </div>
             </div>
           </div>
         </template>
       </transition-group>
       </div>
     </div>
   </div>
<style>
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-enter-to,
.v-leave-from {
opacity: 1;
}

.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}

</style>

There is a jump where the first item is immediately there and the rest fade. I know with a normal there are transition modes but here something isn’t working right.

I‘m relatively new to vue and it‘s declarative programming model is still a bit confusing because I‘m used to traditional dom manipulation.

I don’t have much experience of transitions in Vue, but what seems weird to me is that collection is null initially. I suspect that will make v-for="(product, index) in collection.products" go crazy/crash. Try guarding the HTML code related to showing all products with an if, so it only runs when all products have been fetched.

I updated my OP a little bit, this is how it actually looks. What happens is the api returns a new collection object, this is immediately visible in the DOM and the old html fades out. Each time the DOM is re-rendered, the new collection object is immediately visible and the rest fade out or in.

Can you provide an example or screen recording? Animations are hard to debug without any visuals. I suspect this: v-if="index === 2" is why the first item jumps in.

That didn’t fix it either. I have an api endpoint of products in a collection, and when a filter is applied a fetch request is sent out and the response of new filtered objects is set to the collection again. I did also get rid of the if statement. Basically what happens is the new object returned is always immediately visible and anything leaving the dom transitions, anything entering does not.

Here is a link:
https://drive.google.com/file/d/1MKIdLuEXl7R_tny-BfwNSJabrNVm6irJ/view?usp=sharing

You need to account for the items that are being removed and transition the ones that aren’t. Items that are being removed need to be taken out of the natural layout flow so that the existing items can recalculate their position and move to that spot correctly.

Have a look at the moveClass property and this example in the guide.