vuejs3vue-transitionsrecursive-component

Vue 3 recursive components with transition and transition-group inside


I have two components, CommentList, and CommentItem. Every comment can have replies, so it is recursive.

Here are their templates:

CommentList:

<template>
<!-- other code -->
      <transition name="fade-list" mode="out-in">
        <transition-group
          v-if="comments.length"
          name="fade"
          tag="ul"
          appear
        >
          <CommentItem
            v-for="(comment, index) in comments"
            :key="comment.id"
            :comment="comment"
          />
        </transition-group>
      </transition>
<!-- other code -->
</template>

CommentItem:

<template>
  <li>
<!-- other code -->
      <transition name="fade-list" mode="out-in">
        <transition-group
          v-if="comment.replies"
          name="fade"
          tag="ul"
          appear
        >
          <CommentItem
            v-for="reply in comment.replies"
            :key="reply.id"
            :comment="reply"
          />
        </transition-group>
      </transition>
<!-- other code -->
  </li>
</template>

import CommentListTransition from './CommentListTransition.vue';

export default {
  components: {
    CommentListTransition,
  },
}

In such case all works. But then I decided to move the transition block to a separate component and the comment replies stopped showing:

CommentList:

<template>
<!-- other code -->
      <CommentListTransition
        :comments="comments"
      />
<!-- other code -->
</template>

CommentItem:

<template>
  <li>
<!-- other code -->
      <CommentListTransition
        v-if="comments.replies"
        :comments="comments.replies"
      />
<!-- other code -->
  </li>
</template>

import CommentListTransition from './CommentListTransition.vue';

export default {
  name: 'CommentItem',

  components: {
    CommentListTransition,
  },

  props: {
    comment: {
      type: Object,
      required: true,
    },
  },
}

CommentListTransition:

<template>
  <transition name="fade-list" mode="out-in">
    <transition-group v-if="comments.length" name="fade" tag="ul" appear>
      <CommentItem
        v-for="(comment, index) in comments"
        :key="comment.id"
        :comment="comment"
      />
    </transition-group>
  </transition>
</template>

<script>
import CommentItem from './CommentItem.vue';

export default {
  components: {
    CommentItem,
  },

  props: {
    comments: {
      type: Array,
      default: () => [],
    },
  },
};
</script>

<style>
.fade-enter-active,
.fade-move {
  transition: 0.4s ease all;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: scale(0.6);
}

.fade-leave-active {
  transition: 0.4s ease all;
  position: absolute;
}

.fade-list-enter-active,
.fade-list-leave-active {
  transition: 0.4s ease all;
}

.fade-list-enter-from,
.fade-list-leave-to {
  transform: translateY(10px);
  opacity: 0;
}
</style>

Of course this can be implemented via slot:

<template>
  <transition name="fade-list" mode="out-in">
    <transition-group name="fade" tag="ul">
      <slot></slot>
  </transition>
</template>

But I would like to call CommentItem inside CommentListTransition.

How can this be fixed?

I tried to put transition, and transition-group with comments in a separate component, but the child comments stopped displaying.

I want all comments to be displayed, both parent and child comments.


Solution

  • I tried your code and the only thing I change is displayed Comments and your animation works fine. (I passed your components in Composition Api It wasn't working in OptionsComponent used before initialization)

    The object I use in App.vue I hope it's the good form you want or the comments object

    <script setup lang="ts">
      import CommentList from './CommentList.vue';
    
      let id = 0;
    
      const comments = [
        { id: id++, comment: 'test', replies: [{ id: id++, comment: 'test2' }] },
        {
          id: id++,
          comment: 'test3',
          replies: [
            {
              id: id++,
              comment: 'test4',
              replies: [
                { id: id++, comment: 'test5' },
                { id: id++, comment: 'test6' },
              ],
            },
          ],
        },
      ];
    </script>
    <template>
      <CommentList :comments="comments" />
    </template>
    

    CommentList.vue

    <template>
      <!-- other code -->
      <CommentListTransition :comments="comments" />
      <!-- other code -->
    </template>
    
    <script setup lang="ts">
      import CommentListTransition from './CommentListTransition.vue';
    
      defineProps<{
        comments: any[];
      }>();
    </script>
    

    CommentListTransition.vue

    <template>
      <Transition
        name="fade-list"
        mode="out-in"
      >
        <TransitionGroup
          v-if="comments.length"
          name="fade"
          tag="ul"
          appear
        >
          <CommentItem
            v-for="comment in comments"
            :key="comment.id"
            :comment="comment"
          />
        </TransitionGroup>
      </Transition>
    </template>
    
    <script setup lang="ts">
      import CommentItem from './CommentItem.vue';
    
      defineProps<{
        comments: any[];
      }>();
    </script>
    
    <style>
      .fade-enter-active,
      .fade-move {
        transition: 0.4s ease all;
      }
    
      .fade-enter-from,
      .fade-leave-to {
        opacity: 0;
        transform: scale(0.6);
      }
    
      .fade-leave-active {
        transition: 0.4s ease all;
        position: absolute;
      }
    
      .fade-list-enter-active,
      .fade-list-leave-active {
        transition: 0.4s ease all;
      }
    
      .fade-list-enter-from,
      .fade-list-leave-to {
        transform: translateY(10px);
        opacity: 0;
      }
    </style>
    

    CommentItem.vue

    <template>
      <li>
        <!-- other code -->
        <p>{{ comment.comment }}</p>
        <CommentListTransition
          v-if="comment.replies"
          :comments="comment.replies"
        />
        <!-- other code -->
      </li>
    </template>
    <script setup lang="ts">
      import CommentListTransition from './CommentListTransition.vue';
    
      defineProps<{
        comment: any;
      }>();
    </script>
    

    With this code, all comments are displayed, and I got the transition on each. Are you sure you assign a valid ID to your comment ? Hope it helps you :).