Subscribe on changes!

Event bubble / leak to grand parent issue

avatar
Oct 20th 2022

Vue version

3.2.38

Link to minimal reproduction

https://kasheftin.github.io/vue3-event-bubble-leak/

Steps to reproduce

  • drag the item around
  • watch (console.log) the @change event triggers every time when items are resorted, hovewer there's no $emit(change) in the target component and the target component explicitly sets emits:['run'].

What is expected?

Events should not leak from the child component to the grand parent. That's how it was in vue2.

What is actually happening?

@change event bubble/leak happens every time when 3-rd party component triggers something inside.

System Info

No response

Any additional comments?

Basically there're 3 components: root -> child -> draggable. The root component has @change event listener. The child component has nothing and it explicitly sets emits: ['run'] meaning it can emit run event only. It can not emit change event. The draggable component is 3rd party. It does not matter how it works and what it emits. All that matter is it does not have @change event listener in the child component.

Basically, the vue2 logic was pretty straight-forward and simple. If a grand-child requires some property from grand-parent, it had to be specified in the intermediate component and passed down. The same happend with events - a child emits @change event which is required to be caught on grand-parent, then the intermediate component had to have explicit @change=$emit('change', $event). It was a bit verbose, but it worked like a charm.

avatar
Oct 20th 2022

In Vue 3, all event listeners passed to a component which are not declared in that component's emits, will be applied as native event listeners to the root element.

https://vuejs.org/guide/components/attrs.html#v-on-listener-inheritance

In Vue 2, you could create the same issue by adding the .nativelistener:

<Child @change.native="handler" />

Now, is it a bit easier to accidentally pass a native event when you don't want to in Vue 3 compared to Vue 2? Yes. But it's not a bug, you can handle it yourself.

So that's how it works for now. We can think about changing it someday in Vue 4, but for the foreseeable future that's how it is.

avatar
Oct 21st 2022

Thanks a lot! You are correct - if emits option includes change, then the @change event does not leak to the grand parent. But a few questions still remain:

  1. Since it worked differently in vue2, would not it be reasonable to implement a configurable setting defining if the non-specified event should pass through the component? There's inheritAttrs prop, it might be something like passChildEventsThrough default to true. For example, imagine there's a chain root -> parent -> child. Assume that root has @event1 event listener defined, while parent has emits=[event2]. Then child emits event1. It will be silently passed through to the root element. Basically, any third party can emit any event and there's no way to stop it from being passed. emits option works like a black-list while I need a white-list so only specified events can go through.

  2. The https://kasheftin.github.io/vue3-event-bubble-leak/ code was updated. emits prop is not specified intentionally. The question is about this line: https://github.com/Kasheftin/vue3-event-bubble-leak/blob/61784ac38a3a13f9c38ef94bf45ddf8db50c6aa4/src/Test1.vue#L6. Some component has @change event listener. It's natural to expect that any change event emitted inside will be caught by the listener. However this does not happen. The demo shows that while an item is being dragged, the change event popups up in the grand parent, going around change event listener in Test1.vue. Then, only on mouseup, the correct change event happens. I would be grateful for any clarification of what's going on there.

avatar
Oct 21st 2022

About 1: I think ìnheritAttrs: false is allowing for what you want, pretty much.

All you would maybe need to do is filter any properties starting with on[A-Z] from $attrs and you have an object that contains only parent attributes, not parent listeners.

About 2: Seems like the implementation of that <draggable> component triggers a native change event -which bubbles up- at a different point in time before it emits a custom change event. A general recommendation would be to not use event names that mirror native events that are also triggered. While I am aware you are not in control of this library's implementation, you could emit a differently named event yourself.

If you have additional questions towards this specific example, please use the repo's discussion feature or our discord to discuss it with the wider community.