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.

10 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.

  3. Thank you Łukasz, I did not know about the event bus being able to fire events to already destroyed components. After quite some time of debugging I am glad that I found your article.

  4. I think you just described a misuse of a global event bus and called it a flaw in the concept of global event bus. You should use regular old vue events in that situation. You should abide the “event up, prop down” motto as much as possible. Global event bus is for global events. Events that are related to the whole application. Maybe like a “logout” event so that all the components may want to save some data to local storage. This usage of a global event bus is imo, the reason you shouldn’t need namespaces. Global event bus is in global scope and global scope is a single namespace. Lack of namespaces force you to keep any non-global name out of global scope which is a good thing.

    The very idea of events is to handle large projects in a way that the components don’t need to know about each other. Using vuex for global events requires moving methods which only affect a single component, to global scope. This is the equivalent of calling a component’s method from another one. More precisely, just using global functions for a bunch of functionality. Which will result in arranging the events in a way that lets you know which component they belong to hence namespaced event names. Global events shouldn’t belong to any component.

    Imagine you have a list of categories along with the respective item counts, in percentage of total on the left sidebar and you navigate through them by clicking on each category. Then the list of items belonging to that category shows up in the main container along with “remove” buttons for each item. The container and the side bar are different components. You want to update the item count whenever you remove one. You also do a bunch of other work on other components after that.
    If you use vuex, you will need to call an action that changes the percentages. What happens if the side bar decides to not do that anymore? The action that updates the percentages becomes useless, you will want to remove it from the application. Then all the components that may have used that action will be broken. You should find and remove all usage of that action. That is not a good practice. Best case, you use one action for the event and all the methods are called from vuex. It looks a lot better but still a severe case of messed up encapsulation. You are still reading some of the component’s data from global scope. May the sidebar check the server for the percentages during sync, it will have to call vuex to change its own data.
    In contrast, a global event bus couldn’t care less who is doing what with the event. If the sidebar doesn’t care for the event anymore it’ll just simply stop listening to it and remove the method that handles it. Then, even if some components still emit that event, no unnecessary method will be run and nothing’s gonna break. The event bus may still want to dispatch that event but it is a lot less work than what could be done in those events if you don’t remove them when not needed.

    We should use things for what they are. Vuex is global scope. Methods and properties that semantically need to be in global scope goes there. You can have a lot of them and may need to group them but if those groups indicate certain components then those methods and properties belong to those components as well.

    Same goes for a global event bus. It is for global events. Any event that doesn’t concern the application globally needs to be in the component they do concern.

Leave a Reply

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