3

I have a custom event to close a modal window and it works perfectly as long as the component is a direct child of the parent. But when I want to call the event from a deeper nested component it doesn’t work.

Here is my parent component:

<Modal @modal-close="close" />

Here is my close button inside <Modal>:

<a @click="$emit('modal-close')">CLOSE</a>

As said, this works. But inside <Modal> I’m importing a component for the content of <Modal> and now I want to call the event from inside the content component like this:

const emit = defineEmits(['modal-close']);
emit('modal-close');

I get no warning or errors. But the parent-parent component <Modal> does nothing. Are events not available globaly? How can I solve this problem?

SOLVED:

I chose the non-event based way by creating a function in the parent for closing the <Modal> and pass it as a prop to the children. It seems to me that this is the simplest solution and also provides flexibility to add custom actions when closing the modal.

This is my parent component now:

const close = (data) {
   // close the modal
   // process data
};

<Modal :close="close" />

And my child component:

const $$ = defineProps({
    close: Boolean | Function,
});

<a @click="$$.close">CLOSE</a>
NicOwen
  • 171
  • 1
  • 3
  • 8

1 Answers1

3

One way (probably the simplest) is to get the intermediary components to bubble the events up. That would mean that a component listens to the modal-close event, and emits it to its parent. This may be the simplest way, but it's not very scalable and something you might do for a one-off use.

Vue 3 doesn't provide a global listener (event bus in vue2), but you could use a library like mitt to handle global events.

_Event Bus

The event bus pattern can be replaced by using an external library implementing the event emitter interface, for example mitt or tiny-emitter._

docs

There is another, non-event based option though.

Instead of listening to an event, you can use a ref variable that will manage open closed state. Modals are often handled as singletons/globals so the variable can be accessible globally. If you have a single modal, you can export that variable from the component or you can use Provide/Inject.

Daniel
  • 34,125
  • 17
  • 102
  • 150
  • Thanks! I chooe the non-event based option and created a method in the parent which I pass as a prop to the children to call it from there. – NicOwen May 03 '22 at 12:44
  • 1
    That will work, but I would advise against passing methods as props. If your modal is a singleton, you can just call an exposed method from anywhere. If you're using vuex/pinia already, you can also use the action and states to manage the modal. – Daniel May 03 '22 at 15:18
  • Thanks for your advise, but why is it not a good idea to pass methods as props? (Sorry, new to Vue … :) – NicOwen May 05 '22 at 14:28
  • It's considered an anti-pattern in Vue (even though common in react) I guess the idea is that the Vue component has convention of a certain way of being updated. By exporting a function like that to give access to some internal functionality, it circumvents the convention so it is less intuitive for others' looking at your code, and the browser dev tool would not be able to track the action the same way as an emit. But I suppose that could be a concern for other workarounds to get this working anyway. – Daniel May 05 '22 at 15:24