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

"Passing the v-model property to a child component in Nuxt.js: A step-by-step

I seem to be having trouble passing dynamically modified properties from layouts into the <Nuxt /> component. This is my ~/layouts/default.vue <template> <div> <input v-model="myprop" /> <span>{{myprop}}< ...

What is the process for binding an absolute path instead of a relative path in a script that includes Node and Perl calls?

In my script, there is a function with the following code: def tokenize(latex,kind='normalize'): output_file = './out.lst' input_file = './input_file.lst' cmd = "perl -pe 's|hskip(.*?)(cm\\|in& ...

Issue with height in self-invoking function not functioning correctly

Issue with height not functioning correctly inside self-invoking function, but works fine within (document).ready(function() (function($){ var clientHeight = document.getElementById('home').clientHeight; alert(clientHeight); })(jQuery); <di ...

Using Backbone.sync to customize dataType

I am working on an application that utilizes a .CSV file as the primary data source for a Backbone Model. I am interested in finding the most effective approach to changing the sync method so that it uses dataType "text" instead of "json". Any insights on ...

What is the best way to eliminate base tags from jQuery mobile scripts?

In jQuery Mobile, how can I remove the base href tags from the page and disable base href? Code Snippet: // Function to test for dynamic-updating base tag support function baseTagTest() { var fauxBase = location.protocol + "//" + location.host + l ...

Top Method for Initiating AJAX Request on WebForms Page

Looking for the best way to execute an AJAX call in a WebForms application? While ASP.NET AJAX components are an option, some may find them a bit heavy compared to the cleaner approach in MVC. Page Methods can be used, but they require static methods ...

Ensuring key code functionality in Vue.js for a smart TV application

Recently, I have started exploring Vue.js and would like to implement remote key functionality in my Vue.js project. Can anyone provide guidance on how I can develop an app for smart TVs? ...

Is it possible to use JQuery to target input nodes based on their values?

How can I check if any of the file input boxes in my list have a value using just one selector statement? Is it possible to achieve this with code like the following: $('input:file[value!=null]') Or is there another way to accomplish this? ...

JavaScript: AWS Amplify: Retrieving the User ID (Sub ID) Following User Registration. Post Registration, Not to Be Confused with Sign In

Currently, I am utilizing the authentication module from AWS Amplify. One question that has been on my mind is how to obtain the userID once a user signs up. While it is possible to retrieve the ID after a user signs in, I am specifically interested in re ...

Coloring vertices in a Three.js geometry: A guide to assigning vibrant hues

This inquiry was previously addressed in this thread: Threejs: assign different colors to each vertex in a geometry. However, since that discussion is dated, the solutions provided may not be applicable to current versions of three.js. The latest versions ...

Error Alert: VueRouter has not been properly defined

I am completely new to fullstack development and was recommended to check out this tutorial on creating WedAPI and VueJS. Prior to this, I have only worked with Python, PERL, and C#. The tutorial can be found at: https://www.youtube.com/watch?v=qS833HGKPD8 ...

What's the best approach for implementing TimeInput exclusively in React-Admin?

I discovered this helpful code snippet on the React-Admin documentation that allows me to input both a date and time: import { DateTimeInput } from 'react-admin'; <DateTimeInput source="published_at" /> But now I'm wonderin ...

The click function for the responsive navbar hamburger is not functioning properly

Having some trouble with the code not working in responsive mode. I've tested it on a 600px screen and the hamburger button doesn't seem to work (I click it and nothing happens). I've gone through both the CSS and JS multiple times but can&a ...

Safari is causing issues with HTML5 Video playback

I have a client with a media-heavy website containing numerous video and audio files. While the videos load perfectly on Chrome, Firefox, and IE, they do not load on Safari for Windows. Here's a snippet of the code: <video controls="controls" type ...

Vue version 3 is encountering an issue with a module that does not have an exported member in the specified path of "../../node_modules/vue/dist/vue"

After I updated my npm packages, errors started popping up in some of the imports from the 'vue' module: TS2305: Module '"../../node_modules/vue/dist/vue"' has no exported member 'X' The X instances affected inclu ...

Avoiding page refresh while submitting a form can be tricky, as both e.preventDefault() and using a hidden iFrame seem

I've been stuck on this issue for a while now, scouring Stack Overflow and Google for solutions, but nothing seems to be working. My main goal is to avoid page reloads after uploading a file because it resets the dynamic HTML I need to display afterwa ...

I'm having trouble getting my bot command handler to function properly

When it comes to my command handler, the commands function properly. However, when I attempt to include arguments like $user-info @user instead of just $user-info, it returns an error stating that the command is invalid. Code //handler const prefix = &ap ...

Creating functionality with a native JavaScript plugin within a directive and test suite

I have a custom JavaScript plugin that is integrated within a directive and utilized in an Angular manner. Snippet of the directive, export default function () { 'use strict'; return { restrict: 'E', scope: { map: &apo ...

Typing in Text within Kendo Textbox using Protractor

I'm encountering an issue with Protractor while trying to input text into a Kendo TextBox. The error message I receive is "ElementNotVisibleError: element not visible". Interestingly, when the text box is clicked on, the "style="display: none;" change ...

Press the button to update several span elements

Imagine I have multiple span elements like this: <span>A</span> <span>B</span> <span>C</span> <span>D</span> and a div element (which will be converted to a button later) named "change". <div id="chan ...