Vue.js: why event bus is bad idea

In February 2019 I wrote a post about global event bus in Vue.js. It’s a pattern to achieve very common thing: transform data not only from parent to child components, but also in reverse order. Event bus allows us to also emit events from parent to children and simply use methods for them. Unfortunately, because of many reasons, it isn’t good pattern and we should avoid it, if there are more possible options to use. After year and a lot of new experiences I can say: event bus is antipattern and should be avoided.  Why? In this short post, I will try to explain this in the simplest way I can. Of course, I open for discussion about that, so feel free to comment and write about your experiences.

For reminder, how can we use event bus and how it works: we must create new Vue instance in separate file and export it. Then, we can import this file in many components and declare our events using $emit and $on methods. There is very simple example:

// eventBus.js 
import Vue from 'vue'
export const eventBus = new Vue()


// First component
import {eventBus} from '@/global/eventBus'
(…)
methods: {
    myMethod (data) {
        // Do something
        eventBus.$emit('my-global-event', data)
    }
}
 

// Second component
import {eventBus} from '@/global/eventBus'
(…)

// Or created, or beforeMount
mounted () {
    eventBus.$on('my-global-event', this.actionForGlobalEvent)
}

It’s the simplest example, but it will work: we create new event bus (new Vue instance), import it in our component and set emits and actions. It’s simple, clear and very easy to read. So, why is it a bad option?

Event bus issues

First, naming. One event bus instanton is not separated by any namespace, so, it’s easy to have many events with the same name. Of course, we can create many instances, for example one for components group, but it will be a bigger problem when our application grows. We can also establish naming convention used in app, for example events: mycomponent/myEvent or mycomponent_myevent etc. Yes, it will work, but we must always remember about that, our whole team must remember about that. But it isn’t the most important reason, why I don’t like event bus now.

The biggest problem are events, $on declarations. We will use them but… what about $off? We also must remember about that and use it on our component beforeDestroy methods. Why? Because without that, methods binding to event bus events will be called even if we destroy component! It’s ok, because we bind them using… yes, global event bus, they still exist inside it and will work. So, we must always remember about removing them from code – and when our app grows, it will be a terrible issue and terrible thing to debug and fixing.

How can it cause problems in real usage? I will describe from my experience: there is parent component List, which display many child elements. We can click each element to change route / move to child details and go back to list. Both use event bus to send some data / event in two-directional way. Also, child component includes sub-child component with method, which emit global event – this event is handled by List (so root parent). And the issue: if we forgot about using $off and then go to one child, go back to list, go to second child, go back to list, go to third child and call method with global event… List will receive not one, but three emits with different data! In many cases, this may indicate incorrect data.

Use Vuex instead of event bus

Yeah, you can just remember about using $off but… you have only illusionary control about that, and it’s very easy to lose it. What can I recommend? The best option is to use Vuex instead of event bus. It provides namespacing, so there is no problem with many similar or the same methods. It has clear, defined state, so we know, what can happen in our application. It provides clear dispatches, actions and commits, data from it is in computed, so we will see changes without any issues. If we remove/destroy component, it will not use any methods after changes in Vuex: one exception is to push function inside Vuex, but, you must do it directly. In summary: Vuex is clear. Is also simple, but give you better control.

8 thoughts on “Vue.js: why event bus is bad idea

  1. No brother, you are wrong. Sometimes there is no way you can use Vuex instead of EventBus.

    Imagine you have a large data coming from an API (Component1-Parent) and you need to update some property of that data from a Child 2: Parent->Child1->Child2.

    Tell me, how can you achieve that with Vuex…unless you call the API from the store and update the data from the store, which is a very bad idea in regardless of performance. The only way to achieve that is by reemiting childs or simply using a Global Event. Think about it

    Regards

    1. Sorry, but I can’t imagine such structure only by description – can you provide it in better way?
      I’m pretty sure, that there is always to use better method than global event bus – wrote only short post about that, but changed many cases during real work.

  2. Say I have the following situation. I have a button a few components deep that should trigger a modal. Right now I would keep the modal and the button in the same component. If I move my button to another component, the modal has to move as well.

    With the eventbus I would emit an event like ‘model.insertModalName.show’ with the button and trigger the modal with the event. And put the modal in the app component. That way the modal is globally available and I don’t have to duplicate components or move them around entirely.

    What are your toughts about that?

    1. You can store modal states in vuex e.g.:

      modalsState: {
      myModal1: false
      }

      And dispatch action to toggle this value on button click + use modalsState.myModal1 from vuex in any component you want to contol modal visibility.
      In such case, your modal can be anywhere, without using eventBus.

      1. It is a nice solution but I am already maintaining state from within the modal itself to animate and display it. So is the vuex way not adding another property to keep in sync? Or is it possible to bind it to the modal state with a getter or mutation?

    2. I’m not sure I understand you correctly, but that “visibility” state can control you use or not component, so you can also use lazy component loading and not attach any modal code – modal will use its state only if it’s enabled (by clicking button). Vuex is common element here, shared between all components.

      1. What I meant was: I have a modal with an data attribute “active” and methods show() and hide() that set active true or false and do some other stuff. So if I were to use vuex I would import the active boolean with the mutation and getter.
        Thank you for your replies, this is something to keep in mind next time I need a global modal or otherwise shared state.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.