Subscribe on changes!

Unable to listen for event handler in $attrs when event is defined in $emits

avatar
Apr 14th 2023

Vue version

3.2.47

Link to minimal reproduction

https://codesandbox.io/s/late-voice-pj0bhv?file=/src/components/HelloWorld.vue

Steps to reproduce

  • Create a child component that listens for an event using this.$attrs / useAttrs() and renders a button conditionally depending on whether an event listener is present from the parent
  • Define the event in this.$emits / defineEmits
  • The button doesn't render
  • Remove the event from this.$emits/defineEmits
  • The button does render
  • Causes a type error when used within a typescript project

What is expected?

  • The button should render because an event listener is present on the parent

What is actually happening?

  • The button doesn't render if the event is defined in emits

System Info

System:
    OS: Linux 5.19 Ubuntu 22.04.2 LTS 22.04.2 LTS (Jammy Jellyfish)
    CPU: (16) x64 Intel(R) Core(TM) i7-10875H CPU @ 2.30GHz
    Memory: 49.27 GB / 62.41 GB
    Container: Yes
    Shell: 5.1.16 - /bin/bash
  Binaries:
    Node: 16.19.0 - ~/.nvm/versions/node/v16.19.0/bin/node
    Yarn: 1.22.19 - ~/.nvm/versions/node/v16.19.0/bin/yarn
    npm: 8.19.3 - ~/.nvm/versions/node/v16.19.0/bin/npm
  Browsers:
    Chrome: 110.0.5481.177
    Firefox: 112.0
  npmPackages:
    vue: ^3.2.47 => 3.2.47

Any additional comments?

The demo contains a couple of examples; one where the button renders (when the event is removed from emits) and two where the button doesn't render (event is defined in emits).

Surely this isn't expected behaviour?

avatar
Apr 15th 2023

It is. just like declared props, declared events are not part of attrs. Otherwise, these events would all automatically be passed onto the component's root element as fallthrough attributes.

Currently though, unlike for props, there is no dedicated api to check wether a listener for a declared event has been passed to a component.

Valid alternative: declare the event as a prop instead - event listeners are, internally, just props with a on[A-Z] naming:

props: {
  msg: String,
  onClick: Function,
  onChange: Function,
}

Then you can use v-if="onClick" etc.

Other alternative would be to not declare the events at all (and use inheritAttrs: false if they should not fall through), then they will be part of attrs.

Also related feature request: #7719

avatar
Apr 17th 2023

Hi @LinusBorg thanks for your reply.

I'm glad we understand that this solution is not the best and glad to see another issue in place to resolve.