DEV Community

loading...
Cover image for Vue-emit: an npm package to dispatch events in Vue.js

Vue-emit: an npm package to dispatch events in Vue.js

denisinvader profile image Mikhail Panichev ・3 min read

Source code | NPM

Vue.js provides an API for dispatching events in components by this.$emit. But in functional components, you don't have this instance at all and many people who didn't previously worked with functional components, ask "How to emit an event in a functional component?"

The first solution is to use props for event listeners. It such a React way. The problem that in this case, you have strictly defined event name, can't use @ / v-on / v-model syntax and have some additional difficulties in implementing transparent wrappers. Example:

// Child component
export default {
  name: 'base-input',
  functional: true,
  props: {
    onInput: {
      type: Function,
      default: () => (),
    },
  },
  render (h, context) {
    return (
      <input
        onInput={context.props.onInput}
        {...{
          attrs: context.data.attrs,
          on: context.listeners,
        }}
      />
    );
  },
};
<!-- Parent component template -->
<template>
  <base-input onInput="doSomething" />
</template>

The second solution is to use context.listeners. This is a good way to dispatch an event in Vue.js. Example:

// Child component
export default {
  name: 'base-input',
  functional: true,
  render (h, context) {
    return (
      <input
        onInput={e => context.listeners.input(e.target.value)}
        {...{
          attrs: context.data.attrs,
          on: context.listeners,
        }}
      />
    );
  },
};
<!-- Parent component template -->
<template>
  <base-input @input="doSomething" />
</template>

But there are some pitfalls when using context.listeners. I've found two situations when you will get TypeError: context.listeners.yourEvent is not a function

1. Listener can be undefined (not proveded)

If you don't pass a listener from the parent component, there is no handler for the event (input event in our case) in context.listeners object.

2. Listener value can be a function

Components structure example

The picture above shows the example structure of components and listeners. It this structure context.listeners.input in the C component will be an array of two functions: [doSomethingA, doSomethingB]. This is also true for this.$listeners.

So when using context.listeners (either directly calling handlers from this.$listeners) you have to always check that listener exists and what type it has: Function or Array.

Using vue-emit

To solve above problems I've wrote the helper function that do all checks and provides the API similar to $emit. Thats how to use it:

yarn add vue-emit
import emit from 'vue-emit';

export default {
  name: 'base-input',
  functional: true,
  render (h, context) {
    return (
      <input
        onInput={e => emit(context.listeners, 'input', e.target.value)}
      />
    );
  },
};

The first argument is an object of listeners with eventName => handler structure. It is a standard Vue this.$listeners and context.listeners objects.

The second argument is an event name. Vue-emit will check listeners object and do nothing if handler is not defined or will call each handler if there are more than one listener.

You can also pass any amount of additional parameters and they all will be passed to handler. By the way, this is the use case with stateful components - when for some reason, you want to pass more than one argument to handler function as a payload.

Conclusion

I hope you will find this package useful and enjoyed reading this article. Feel free to ask your questions, open issues, pull request etc.

GitHub logo denisinvader / vue-emit

Helper function for emitting events in Vue.js (functional) components

Vue emit

Helper function to emit events from Vue.js functional components. Can be used in regular components too CodeSandbox demo

Installation

yarn add vue-emit
# or
npm install vue-emit

Usage

import emit from 'vue-emit';
export default {
  functional: true,
  render (h, context) {
    return (
      <button
        onClick={e => emit(context.listeners, 'someEvent', e, 'additional param', 'etc')}
      >
        {context.children}
      </button>
    );
  },
};

Params

emit(handlers_list, event_name, [optional_payload]);
  • handlers_list - object of functions. In Vue this is this.$listeners and context.listeners (for funtional components). Each value may be a function or an array of functions. In case of array emit will call all provided callbacks.

  • event_name - name of the event to fire. The event may not…

Photo by Daniele Riggi on Unsplash

Discussion (2)

pic
Editor guide
Collapse
mornir profile image
Jérôme Pott

The syntax reminds me a lot of React 😯

Collapse
denisinvader profile image
Mikhail Panichev Author

Yes, me too. It's more functional style than in regular Vue. But the amount of code is still smaller than React 🙂