javascriptvue.jsvuejs3

How to open modal located in child component from parent when button clicked?


Parent has a button. When button is clicked how can we let the Child component know to open the modal?

Parent.vue

<script setup>
import Child from "Child.vue";
</script>

<template>
  <button onClick=""> // open modal when button clicked
  <Child/>
</template>    

Child.vue

<script setup>
import { ref } from "vue";

const openModal = ref(false);
</script>

<template>
  <div>example</div>
</template>

Solution

  • Sharing a variable with the parent

    In the Child component, you can use defineExpose to send the status reactive variable accessible to the parent.

    Example

    or live CDN example:

    const { createApp, ref, defineExpose } = Vue;
    
    // Child
    const ChildComponent = {
      template: `
        <div v-if="status">
          <div>
            <p>Modal is open!</p>
            <button @click="closeModal">Close</button>
          </div>
        </div>
      `,
      setup() {
        const status = ref(false);
    
        const closeModal = () => {
          status.value = false;
        };
    
        defineExpose({ status });
    
        return { status, closeModal };
      }
    };
    
    // Parent
    const App = {
      components: { ChildComponent },
      setup() {
        const childRef = ref(null);
    
        const openChildModal = () => {
          if (childRef.value) {
            childRef.value.status = true;
          }
        };
    
        return { childRef, openChildModal };
      }
    };
    
    createApp(App).mount("#app");
    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
    
    <div id="app">
      <button @click="openChildModal">Open Modal</button>
      <child-component ref="childRef"></child-component>
    </div>

    Notify the parent about an event

    Nevertheless, I believe it is bad practice to extract a function from the component, as in the given example. A component should be self-contained, fully reusable at any time, and minimally affected by external factors. Therefore, I would place both the open and close buttons inside the child component, allowing the parent component to be informed about their clicks through emits.

    You can call the emit from the child component, and when it is triggered, the parent component will be notified, allowing you to build custom functions in response.

    Example

    or live CDN example:

    const { createApp, ref, defineEmits } = Vue;
    
    // Child
    const ChildComponent = {
      template: `
        <button v-if="! status" @click="openModal">Open Modal</button>
        <div v-if="status">
          <div>
            <p>Modal is open!</p>
            <button @click="closeModal">Close</button>
          </div>
        </div>
      `,
      setup(_, { emit }) {
        const status = ref(false);
    
        const openModal = () => {
          status.value = true;
          emit('opened');
        };
        
        const closeModal = () => {
          status.value = false;
          emit('closed');
        };
    
        return { status, openModal, closeModal };
      }
    };
    
    // Parent
    const App = {
      components: { ChildComponent },
      setup() {
        const customFnForChildOpen = () => {
          console.log('modal opened');
        };
        
        const customFnForChildClose = () => {
          console.log('modal closed');
        };
    
        return { customFnForChildOpen, customFnForChildClose };
      }
    };
    
    createApp(App).mount("#app");
    <script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
    
    <div id="app">
      <child-component @opened="customFnForChildOpen" @closed="customFnForChildClose"></child-component>
    </div>