Using socket.io with Vue and Vuex
- JS
- Websockets
- Vue
- Vuex
When building stagetimer.io I decided to use WebSockets to push updates to all connected devices. The app is built on top of Vue using Vuex for state management. For WebSockets, I am using the popular and easy to use socket.io library.
Contents
- The desired Result
- Existing Plugins
- Instantiating the Socket
- Using the Socket with Vue
- Using the Socket with Vuex
The desired Result
I want to use socket.io inside Vue components as well as in the Vuex store. It must also reuse the same socket connection in both cases.
Inside Vue components I want to have access to the socket instance through this.$socket
and use it to listen to and emit messages like this:
export default {
created () {
this.$socket.on('message', this.receiveMessage)
},
destroyed () {
this.$socket.removeAllListeners('message')
}
methods: {
sendMessage (payload) {
this.$socket.emit('message', payload)
},
receiveMessage (payload) {
console.log('received a message', payload)
},
}
}
Inside the Vuex store I want to do the same, except that a received event should translate into a store action like so:
return new Vuex.Store({
state: { ... },
getters: { ... },
mutations: { ... },
actions: {
sendMessage (context, payload) {
this.$socket.emit('message', payload)
},
receiveMessage ({ commit }, payload) {
commit('doSomethingWithMessage', payload)
},
},
})
Existing Plugins
There is no lack of socket.io plugins for Vue. vue-socket.io looks like a good solution. However, I try to avoid 3rd party dependencies if I can easily implement their function myself, as in the case of socket.io.
Instantiating the Socket
To reuse the socket I need to pass the same instance to Vue and the Vuex store. Basically, I want to import the same instance in two different files, kind of like a singleton. Fortunately, the »JavaScript specification guarantees that [I will] receive the same module instance« if I import the same path twice (Source). This means I can create a simple socket.js
file that exports an instance of socket.io.
// socket.js
import io from 'socket.io-client'
const socket = io('http://localhost:3000')
export default socket
Using the Socket with Vue
Now I can import the socket.io instance into the default main.js
file where Vue gets instantiated. To have this.$socket
available in my Vue single file components I can simply inject the socket into the prototype of Vue.
// main.js
import Vue from 'vue'
import socket from './socket'
Vue.prototype.$socket = socket
new Vue({...})
Using the Socket with Vuex
With Vuex it’s not quite so simple. I need to write a little plugin to have this.$socket
available inside store functions. Additionally, I have to define a mapping between socket events and the store actions they should trigger.
Here is the plugin code:
// websocketStorePlugin.js
export default function createWebSocketPlugin (socket) {
return store => {
store.$socket = socket
socket.on('message', payload => store.dispatch('receiveMessage', payload))
}
}
Instead of mapping socket events with store actions explicitly, I could also just map every event to a store action of the same name. But in my actual app, I like to keep it specific and I also like to use store modules.
I then import this plugin into my main store file, usually store.js
or store/index.js
. The createWebSocketPlugin
method is called with the socket.io instance and passed to the store as a plugin.
import Vue from 'vue'
import Vuex from 'vuex'
import createWebSocketPlugin from './websocketStorePlugin'
import socket from '../socket'
Vue.use(Vuex)
const websocketPlugin = createWebSocketPlugin(socket)
export default new Vuex.Store({
// ...
plugins: [websocketPlugin],
})
And yeah, that’s it!