title: VueJS display dynamic modal component
body: <p>I have posts and replys s.t. replies belong to posts via the attribute reply.posts_id.</p>
  
  <p>I am attempting to show the reply form as a modal for the user to enter a reply. However, I want to create a generic Modal component that I can use everywhere with content that is specified in another component built for a specific context.</p>
  
  <p>Reply to post is the first place I woul like this to work.</p>
  
  <p>Currently, the Vuex correctly returns Modal visible:true when the reply button is clicked, but the modal does not render and I get the error message showing that the Modal component is not found:</p>
  
  <pre><code>Unknown custom element: &lt;ModalReplyForm&gt; - did you register the component correctly? For recursive components, make sure to provide the "name" option.
  </code></pre>
  
  <p>I am using vuex to manage the visibility of the modal. Here are the relevant files:</p>
  
  <p>store.js:</p>
  
  <pre><code>import Vue from 'vue'
  import Vuex from 'vuex'
  import axios from 'axios'
  ...
  
  Vue.use(Vuex)
  
  export default new Vuex.Store({
    state: {
      status: '',
      ...
      modalVisible: false,
      modalComponent: null
    },
    mutations: {
      ...
      showModal(state, componentName) {
        console.log('showing the modal')
        state.modalVisible = true;
        state.modalComponent = componentName;
      },
      hideModal(state) {
        console.log('hiding the modal')
        state.modalVisible = false;
      }
    },
    actions: {
      ...
      }
    },
    getters: {
      isAuthenticated: state =&gt; !!state.user,
      authStatus: state =&gt; state.status,
      user: state =&gt; state.user,
      token: state =&gt; state.token,
      posts: state =&gt; {
        return state.posts;
      }
  ...
    }
  })
  </code></pre>
  
  <p>App.vue</p>
  
  <pre><code>&lt;template&gt;
    &lt;div id="app"&gt;
      &lt;app-modal&gt;&lt;/app-modal&gt;
      &lt;NavigationBar /&gt;
      &lt;div class="container mt-20"&gt;
        &lt;router-view /&gt;
      &lt;/div&gt;
      &lt;vue-snotify&gt;&lt;/vue-snotify&gt;
    &lt;/div&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  import AppModal from '@/components/global/AppModal';
  import NavigationBar from '@/components/layout/NavigationBar'
  export default {
    name: "App",
    components: {
      AppModal,
      NavigationBar
    }
  };
  &lt;/script&gt;
  
  &lt;style&gt;
  body {
    background-color: #f7f7f7;
  }
  
  .is-danger {
    color: #9f3a38;
  }
  &lt;/style&gt;
  </code></pre>
  
  <p>Post.vue (houses the button to call the reply modal):</p>
  
  <pre><code>&lt;template&gt;
    &lt;div class="row ui dividing header news"&gt;
      &lt;!-- Label --&gt;
      &lt;div class="m-1 col-md-2 ui image justify-content-center align-self-center"&gt;
        &lt;img v-if="post.avatar_url" :src="post.avatar_url" class="mini rounded"/&gt;
        &lt;v-gravatar v-else :email="post.email" class="mini thumbnail rounded image rounded-circle z-depth-1-half"/&gt;
      &lt;/div&gt;
      &lt;!-- Excerpt --&gt;
      &lt;div class="col-md-9 excerpt"&gt;
  
  ...
  
          &lt;!-- Feed footer --&gt;
        &lt;div class="feed-footer row"&gt;
          &lt;div class="small"&gt; {{ post.created_at | timeAgo }}&lt;/div&gt;
          &lt;button type="button" flat color="green" @click="showModal('ModalReplyForm')"&gt;
            &lt;i class="fa fa-reply" &gt;&lt;/i&gt;
  
  ...
  
          &lt;div v-show="postOwner(post)" class=""&gt;
            &lt;button type="button" flat color="grey" @click="deletePost(post.id)"&gt;
              &lt;i class="fa fa-trash " &gt;&lt;/i&gt;
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  import { mapMutations } from 'vuex';
  import PostsService from '../../services/PostsService'
  import RepliesService from '../../services/RepliesService'
  import Replies from '@/components/Reply/Replies'
  import ReplyForm from '@/components/Reply/ReplyForm'
  export default {
    name: "Post",
    props: {
      post: {
        type: Object,
        required: true
      }
    },
    components: {
      Replies,
      ReplyForm
    },
    computed: {
      me() {
        return this.$store.getters.user
      }
    },
    methods: {
      ...mapMutations(['showModal']),
      ...
    }
  };
  &lt;/script&gt;
  </code></pre>
  
  <p>AppModal.vue - generic Modal component</p>
  
  <pre><code>&lt;template&gt;
    &lt;div class="c-appModal"&gt;
      &lt;div class="c-appModal__overlay" v-if="visible"&gt;&lt;/div&gt;
      &lt;div class="c-appModal__content" v-if="visible" @click.self="hideModal"&gt;&lt;/div&gt;
        &lt;div class="c-appModal__innerContent"&gt;
          &lt;component :is="component"&gt;&lt;/component&gt;
        &lt;/div&gt;
    &lt;/div&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  import Vue from 'vue';
  import { mapState, mapMutations } from 'vuex';
  
  export default {
    name: 'AppModal',
    data() {
      return {
        component: null
      }
    },
    computed: {
      ...mapState({
        visible: 'modalVisible',
        modalComponent: 'modalComponent'
      }),
    },
    methods: {
      ...mapMutations(['hideModal'])
    },
    watch: {
      modalComponent(componentName) {
        if (!componentName) return;
  
        Vue.component(componentName, () =&gt; import(`@/components/modals/${componentName}`));
  
        this.component = componentName;
      }
    },
    created() {
      const escapeHandler = (e) =&gt; {
        if (e.key === 'Escape' &amp;&amp; this.visible) {
          this.hideModal();
        }
      };
  
      document.addEventListener('keydown', escapeHandler);
      this.$once('hook:destroyed', () =&gt; {
        document.removeEventListener('keydown', escapeHandler);
      });
    },
  };
  &lt;/script&gt;
  </code></pre>
  
  <p>ModalReplyForm - specific reply modal content</p>
  
  <pre><code>&lt;template&gt;
    &lt;div&gt;
      &lt;div class="c-modalReply"&gt;
        &lt;div&gt;
          &lt;label for="reply"&gt;Your comment&lt;/label&gt;
          &lt;div class="field"&gt;
            &lt;textarea name="reply" v-model="reply" rows="2" placeholder="Compose reply"&gt;&lt;/textarea&gt;
          &lt;/div&gt;
        &lt;/div&gt;
  
        &lt;button class="c-modalReply__cancel" @click="hideModal"&gt;Cancel&lt;/button&gt;
        &lt;button class="c-modalReply__post" :disabled="!isFormValid" @click="createReply"&gt;Reply&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  import RepliesService from '@/services/RepliesService'
  import { mapMutations } from 'vuex';
  
  export default {
    name: "ModalReplyForm",
    // props: {
    //   post: {
    //     type: Object,
    //     required: true
    //   }
    // },
    data() {
      return {
        reply: ""
      };
    },
    computed: {
      isFormValid() {
        return !!this.reply;
      },
      currentGroup() {
        return this.$store.getters.currentPost;
      }
    },
    methods: {
      ...mapMutations([
        'hideModal'
      ]),
      async createReply () {
        let result = await RepliesService.addReply({
          reply: {
            body: this.reply,
            postId: this.post.id
          }
        });
        this.$emit("reply-created");
        this.hideModal();
      }
    }
  };
  &lt;/script&gt;
  </code></pre>