Using Vuex as a global event bus ensures that all subscribers will always receive notifications for

For a while now, I have relied on a global event bus in Vue - creating it as const bus = new Vue(). It works well, but managing subscriptions can get tedious at times.

Imagine subscribing to an event in a component:

mounted() {
  bus.$on('some.event', callback)
}

In order to properly dispose of the callback, I need to keep track of it and handle its removal in beforeDestroy. While using a global mixin can simplify this process, things get more complex when dealing with subscriptions made in both mounted and activated callbacks due to my usage of <keep-alive>.

Considering these challenges, I decided to experiment with Vuex for managing the event system. The approach I came up with is detailed below.

Everything seems to work smoothly when publishing objects or arrays. However, primitive data doesn't trigger reactivity even when wrapped in an outer object like { data: 123 }

I'm open to suggestions for better ways to notify subscribers about events. So far, the internal notify method seems risky to rely on.

eventstore.js

import Vue from 'vue'

const state = {
  events: {}
}

const actions = {
  publish({commit}, payload) {
    commit('publish_event', payload)
  }
}

const mutations = {
  publish_event(state, payload) {
    if(!state.events[payload.key]) {
      Vue.set(state.events, payload.key, { data: payload.data })
    } else {
      state.events[payload.key] = { data: payload.data }
    }
  }
}

const getters = {
  events: (state) => state.events
}

export default {
  state,
  actions,
  mutations,
  getters
}

globalmixin.js

methods: {
  publish(key, data) {
    this.$store.dispatch('publish', { key, data })
  }
}

somecomponent.vue

function mapEventGetters(eventKeys) {
  return _.reduce(eventKeys, (result, current) => {
    result[current] = function() {
      return  _.get(this, `$store.getters.events.${current}.data`)
    }
    return result
  }, {})
}
computed: {
  ...mapEventGetters(['foo_bar'])
},
watch: {
  'foo_bar'(value) {
    console.log(`foo_bar changed to ${value}`)
  }
}

Answer №1

The data flow in Vuex is a fundamental concept that should not be disrupted by this API. Allowing clients to mutate or read store state anywhere in Vuex could cause issues with the integrity of the application.

Instead of implementing this method in Vuex, it might be more appropriate to utilize an event emitter, such as an empty Vue instance, within actions. This approach can help maintain the structure and functionality of Vuex without compromising its core principles.

export const emitter = new Vue()

export default {
  // ...

  actions: {
    // called when the store is initialized
     observe({ dispatch, commit }) {
      emitter.$on('some-event', () => {
        commit('someEvent')
      })

      emitter.$on('other-event', () => {
        dispatch('otherEvent')
      })
    },

    notify({ state }) {
      emitter.$emit('notify', state.someValue)
    }
  }
}

This solution was helpful to me when I encountered a similar issue on GitHub. It may assist you as well. Thank you!

Answer №2

One way to ensure that your data remains reactive is by using deepCopy, such as JSON.parse(JSON.stringify()).

const mutations = {
  publish_event(state, payload) {
    if(!state.events[payload.key]) {
      state.events[payload.key] = { data: payload.data }
    } else {
      state.events[payload.key] = Object.assign({}, state.events[payload.key], { data: payload.data })
    }
    state.events = JSON.parse(JSON.stringify(state.events))
  }
}

In the component code provided, there is a listener for foo_bar in the watcher. It's important to note that Vue watchers only work with component data sourced from data, computed, or vuex.

To address this issue, you can redefine your data as componentData as shown below. Additionally, you can utilize mapGetters for a more concise syntax:

<script>
  import { mapGetters } from 'vuex'
  export default {
    ...mapGetters(['events']),
    computed: {
      componentData () {
        const eventKeys = ['foo_bar']
        return _.reduce(eventKeys, (result, current) => {
          result[current] = function() {
            return  _.get(this, `events.${current}.data`)
          }
          return result
        }, {})
      }
    },
    watch: {
      componentData: function (newVal, oldVal) {
        ...
      }
    }
  }
</script>

Answer №3

Utilizing Vue.set to modify an object does not automatically create observers or reactivity for the data within that object. To achieve this, an additional call to Vue.set is necessary.

Vue.set(state.events, payload.key, {})
Vue.set(state.events[payload.key], 'data', payload.data)

For a more streamlined approach, you can encapsulate this functionality into a custom utility function that recursively utilizes Vue.set to set the data.

Answer №4

Can you test this out and confirm if reactivity is triggered in both scenarios?

Start by simplifying the payload structure and removing unnecessary wrapping with an outer object. Send the payload as a basic key/value object with the event key and associated data:

{
  someKey: 123
}

Next, try sending nested data:

{
  someKey: {
    nested: 'Value'
  }
}

Prior to that, make sure to update the mutation code with the following changes:

const mutations = {
  publish_event(state, payload) {
    // Replace the existing code with a simple "patch"
    // to update the state.events with the payload content.
    state.events = { ...state.events, ...payload }
  }
}

Also, don't forget to enhance the mapEventGetters function since data are no longer nested under the "data" property.

PS: Personally, I recommend using Vuex with specific getters because it effectively triggers reactivity with primitive types:

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

const state = {
  events: {}
}

const actions = {
  publish({commit}, payload) {
    commit('publish_event', payload)
  }
}

const mutations = {
  publish_event(state, payload) {
    state.events = { ...state.events, ...payload }
  }
}

const getters = {
  fooBar: state => state.events.fooBar || ''
}

Vue.use(Vuex)

export default new Vuex.Store({
  state,
  actions,
  mutations,
  getters
})

main.js

import Vue from 'vue'
import App from '@/App'
import store from '@/store'

new Vue({
  store,
  render: h => h(App)
}).$mount('main')

some component

<template>
  <span>{{ fooBar }}</span>
</template>

import { mapGetters, mapActions } from 'vuex'

export default {
  name: 'SomeComponent',

  computed: {
    ...mapGetters(['fooBar'])
  },

  methods: {
    ...mapActions(['publish'])
  },

  created () {
    setTimeout(() => {
      publish({
        fooBar: 123
      })
    }, 3000)
  }
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to handle responses in axios when dealing with APIs that stream data using Server-Sent Events (S

Environment: web browser, javascript. I am looking to utilize the post method to interact with a Server-Send Events (SSE) API such as: curl https://api.openai.com/v1/completions \ -H "Content-Type: application/json" \ -H ...

Using AngularJS date picker to set value in Spring model setter

When using AngularJS with Spring, I noticed a discrepancy in the date values between my view file and the POJO User object. In my view file, I have used an input type date to bind the "user.dateOfBirth" attribute. When I select a date, it is displayed cor ...

What is the best way to run a series of basic commands in Node.js in sequence

Is there a way to execute 4 bash commands sequentially in nodejs? set +o history sed -i 's/&& !this.peekStartsWith('\/\/')/ /g' dist/vendor.bundle.js sed -i 's/&& !this.peekStartsWith('\/\/ ...

Having trouble setting a value as a variable? It seems like the selection process is not functioning properly

My Hangman game has different topics such as cities and animals. When a user selects a topic, the outcome should be a random item from that specific topic. For example: London for cities or Zebra for animals. Currently, I am only generating a random lett ...

Is there a method to avoid redeclaring variables in JavaScript with jQuery?

In the structure of my code, I have the following setup. <!-- first.tpl --> <script> $(document).ready(function() { objIns.loadNames = '{$names|json_encode}'; } ) </script> {include file="second.tpl"} <! ...

Is there a way to use JavaScript to rearrange the order of my div elements?

If I have 4 divs with id="div1", "div2", and so on, is there a way to rearrange them to display as 2, 3, 1, 4 using Javascript? I am specifically looking for a solution using Javascript only, as I am a beginner and trying to learn more about it. Please p ...

generate a collection using a string of variables

I'm looking for a way to pass a string as the name of an array to a function, and have that function create the array. For example: createArray('array_name', data); function createArray(array_name, data){ var new_array = []; // pe ...

The Ajax PHP file uploader is experiencing technical difficulties

I am currently working on an ajax image uploader that is supposed to work automatically when a user submits an image. However, I've encountered an issue where the uploader does not function as expected when I try to rename the images for ordering purp ...

Can someone assist me in creating a clickable link that opens a menu in HTML when clicked?

I have been attempting for the past few days to open the megamenu by clicking on a link, but despite my efforts, I have not been successful. After reviewing some code, I discovered a clue in the CSS. It seems that setting the visibility value to visible wi ...

rectangle/positionOffset/position().top/any type of element returning a position value of 0 within the container

While the height/position of the container is accurately displayed, attempting to retrieve the top position (or any position) of containing elements yields a return value of 0. Additionally, using .getBoundingClientRect() results in all values (top, left, ...

What could be causing NestJS/TypeORM to remove the attribute passed in during save operation?

Embarking on my Nest JS journey, I set up my first project to familiarize myself with it. Despite successfully working with the Organization entity, I encountered a roadblock when trying to create a User - organizationId IS NULL and cannot be saved. Here ...

Leveraging Vue.js's capabilities with an external setup file

Is it feasible for a Vue App to access an external configuration file? I envision a setup where I deploy the application along with the config file; then, I should be able to modify the configuration in the external file without needing to rebuild the enti ...

In the console, a JSON string is displayed, however the PHP variable outputs as null

Below is the snippet of javascript code I am working with: $('.showEditForm').click(function () { var webpagefileID = this.id; if($('#editForm').css('display', 'none')) { $('#editForm').css ...

Using jQuery to dynamically add or remove table rows based on user inputs

Apologies if this is too elementary. I am attempting to insert rows into a table if the current number of rows is less than what the user requires. Simultaneously, I need to remove any excess rows if the current number exceeds the user's specificati ...

Exploring the wonders of Angular 2: Leveraging NgbModal for transclusion within

If I have a modal template structured like this: <div class="modal-header"> <h3 [innerHtml]="header"></h3> </div> <div class="modal-body"> <ng-content></ng-content> </div> <div class="modal-footer"& ...

(RESPOND) Configuring a preset option for a Dropdown selection field

I am currently developing a frontend to demonstrate the behavior of a CRUD RESTful API. One specific requirement is that when the user selects the option "GET", the default value in the dropdown field labeled "Order-by" should be set to "Name". However, fo ...

Switch the paper tab to a dropdown menu in Polymer.js

Can someone assist me in transforming the paper tab into a paper drop down menu in polymer JS? I want the drop-down to appear with a list of values when hovering over the Top menu. Activity Execution <paper-tab cla ...

Sending back an HTTP response code from PHP to AJAX

I'm currently working on creating a login page for a website. The functionality involves using AJAX to send a request to a PHP script that verifies the username and password input. If the query returns a successful result, I use http_response_code(200 ...

Retrieve Element By Class Name in JavaScript

How can I modify the border color at the bottom of the .arrow_box:after? Javascript Solution document.getElementsByClassName("arrow_box:after")[0].style.borderBottomColor = "blue"; Despite trying this solution, it seems to be ineffective! For a closer ...

Creating sequential numbers using jQuery

Recently, I worked on a script (credit to Chyno Deluxe) that generates a list of menu items based on what is written in the input box. However, I now have a new requirement which involves generating a sequence of numbers to be added continuously. Here is ...