floriank
floriank

Reputation: 25698

Vue.js - Two components of the same kind won't toggle properly

I have two components inside the template of another component that are toggled depending on the click on a "reply" and "edit" button.

<comment-form :model="model" :model-id="modelId" :comment="comment" v-if="showEditForm && canComment" @closeForm="closeForm"></comment-form>
<comment-form :model="model" :model-id="modelId" :parent-id="comment.id" :reply-to="comment" v-if="showReplyForm && canComment" @closeForm="closeForm"></comment-form>

The issue is now that it never closes the first opened instance for whatever reason. When I click "edit" and then "reply" it seems that the instance of the "edit" stays open instead of closing it and opening the "reply" instance.

These are the methods that toggle the display of the forms:

methods: {
    closeForm: function () {
        this.showReplyForm = false;
        this.showEditForm = false;
    },
    reply: function () {
        this.showReplyForm = true;
        this.showEditForm = false;
    },
    editComment: function () {
        this.showEditForm = true;
        this.showReplyForm = false;
    },
},

I would like to understand the reason for that behavior and how to fix it.

Here is the whole comment.vue file:

<template>
    <div class="comment">
        <div class="header">
            <div v-if="!comment.user">
                <span class="name">{{comment.name}}</span>
                wrote on
                <span class="time">{{comment.created}}</span>:
            </div>
            <div v-else>
                <span class="name">{{comment.user.username}}</span> wrote on {{comment.created}}:
            </div>
        </div>
        <div class="body">
            <template v-for="line in comment.body.split('\n')">{{line}}<br></template>
        </div>
        <div class="footer">
            <a href="#" class="reply-btn" v-on:click.prevent="reply()" v-if="!isLoginRequired || isLoggedIn">
                reply
            </a>
            <span v-if="isMyComment">
                &nbsp;&bull;&nbsp;
                <a href="#" class="edit-btn" v-on:click.prevent="editComment()">
                    edit
                </a>
                &nbsp;&bull;&nbsp;
                <a href="#" class="delete-btn" v-on:click.prevent="deleteComment()">
                    delete
                </a>
            </span>
        </div>
        <comment-form :model="model" :model-id="modelId" :comment="comment" v-if="showEditForm && canComment" @closeForm="closeForm"></comment-form>
        <comment-form :model="model" :model-id="modelId" :parent-id="comment.id" :reply-to="comment" v-if="showReplyForm && canComment" @closeForm="closeForm"></comment-form>
        <comments-list :level="level + 1" v-if="hasChildren" :model="model" :model-id="modelId" :parent-id="comment.id"></comments-list>
    </div>
</template>

<script>
export default {
    props: {
        comment: null,
        modelId: null,
        level: null,
        parentId: null,
        model: {
            type: String,
            default: null
        },
    },
    computed: {
        hasChildren: function() {
            return this.$commentsStore.getters.hasChildren(
                this.model,
                this.modelId,
                this.comment.id,
            );
        },
        canComment: function() {
            return this.$commentsStore.getters.canPost;
        },
        isLoggedIn: function() {
            return this.$commentsStore.getters.isLoggedIn;
        },
        isMyComment: function () {
            return this.$commentsStore.getters.isMyComment(this.comment);
        },
        isLoginRequired: function() {
            return this.$commentsStore.getters.getConfig('loginRequired');
        }
    },
    methods: {
        closeForm: function () {
            this.showReplyForm = false;
            this.showEditForm = false;
        },
        reply: function () {
            this.showReplyForm = true;
            this.showEditForm = false;
        },
        editComment: function () {
            this.showEditForm = true;
            this.showReplyForm = false;
        },
        deleteComment: function () {
            return this.$commentsStore.dispatch('deleteComment', this.comment);
        }
    },
    data: function() {
        return {
            showReplyForm: false,
            showEditForm: false
        };
    }
};
</script>

Upvotes: 4

Views: 93

Answers (1)

Roy J
Roy J

Reputation: 43899

It's kind of crazy, but you need a key. This is the first example I've seen where you need one outside of a v-for.

What's going on is that you have the same component in two v-ifs. When Vue goes to update, it sees that it's supposed to have a comment-form, and it already does. It doesn't recognize the differences. I'd call it a bug in Vue, tbh, but the workaround is to provide a key to each component instance.

new Vue({
  el: '#app',
  data: {
    showEditForm: true,
    showReplyForm: false
  },
  components: {
    commentForm: {
        methods: {
        close() {
            this.$emit('close-form');
        }
      }
    }
  },
  methods: {
    closeForm: function() {
      this.showReplyForm = false;
      this.showEditForm = false;
    },
    reply: function() {
      this.showReplyForm = true;
      this.showEditForm = false;
    },
    editComment: function() {
      this.showEditForm = true;
      this.showReplyForm = false;
    },
  }
})
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <button @click="reply">
    Reply
  </button>
  <button @click="editComment">
    Edit
  </button>
  <comment-form v-if="showEditForm" key="edit" @close-form="closeForm" inline-template>
    <div>
      This is the edit form
      <button @click="close">
        Close it
      </button>
    </div>
  </comment-form>
  <comment-form id="reply" key="reply" v-if="showReplyForm" @close-form="closeForm" inline-template>
    <div>
      This is the reply form
      <button @click="close">
        Close it
      </button>
    </div>
  </comment-form>
</div>

Upvotes: 4

Related Questions