Vue 3 recursive components with transition and transition-group inside

99 views Asked by At

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.

1

There are 1 answers

1
ArrayConstructor On BEST ANSWER

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 :).