Using websockets with redux-saga

Pierre Maoui
Dec 12, 2016 · 3 min read

In this blog post, I share my approach in order to use websockets with redux-saga by using channels.

I assume that you already know all basics stuff about Sagas. If you don’t, read the introduction on the official documentation of redux-saga.

Redux-saga is mainly described as a way of dealing with side-effects (asynchronous calls and so on) performed by an application. Incoming actions from the store invoke a function that will dispatch later an other action back to the store. With websockets, we won’t rely on incoming actions but on external event sources. Let’s see how to achieve this.

Introducing channels

Here is a typical typical watch-and-fork pattern, in order to pull and handle actions :

function* watchRequests() {
while (true) {
const { payload } = yield take('REQUEST')
yield fork(handleRequest, payload)
}
}

We can implement the same flow with actionChannel :

import { take, actionChannel, call, ... } from 'redux-saga/effects'function* watchRequests() {
const requestChan = yield actionChannel('REQUEST')
while (true) {
const { payload } = yield take(requestChan)
yield call(handleRequest, payload)
}
}

With the second pattern, we can take advantage of queueing all non-processed actions (inside requestChan).

With websockets, we also rely on a channel which is based on custom events rather than actions. To achieve this, we can make use of eventChannel.

eventChannel is a function that takes a subscriber to initialize the external event source (it means for us to connect to the websocket server).

Here is a rough example of how we can dispatch an action from a websocket event :

function websocketInitChannel() {  return eventChannel( emitter => {    // init the connection here
const ws = new WebSocket()

ws.onmessage = e => {
// dispatch an action with emitter here
return emitter( { type: 'ACTION_TYPE', payload } )
}
// unsubscribe function
return () => {
// do whatever to interrupt the socket communication here
}
})}export default function* websocketSagas() {
const channel = yield call(websocketInitChannel)
while (true) {
const action = yield take(channel)
yield put(action)
}
}

Websocket code should be isolated with a dedicated middleware at the redux store level :

const websocketMiddleware = createSagaMiddleware()const middlewares = [ /*other_middlewares*/ , websocketMiddleware ]const enhancers = [
applyMiddleware(...middlewares),
// ...
]
// ...
websocketMiddleware.run(websocketSagas)

An example from the real world

function initWebsocket() {  return eventChannel(emitter => {    ws = new WebSocket(wsUrl + '/client')    ws.onopen = () => {
console.log('opening...')
ws.send('hello server')
}
ws.onerror = (error) => {
console.log('WebSocket error ' + error)
console.dir(error)
}
ws.onmessage = (e) => {
let msg = null
try {
msg = JSON.parse(e.data)
} catch(e) {
console.error(`Error parsing : ${e.data}`)
}
if (msg) {
const { payload: book } = msg
const channel = msg.channel
switch (channel) {
case 'ADD_BOOK':
return emitter({ type: ADD_BOOK, book })
case 'REMOVE_BOOK':
return emitter({ type: REMOVE_BOOK, book })
default:
// nothing to do
}
}
}
// unsubscribe function
return () => {
console.log('Socket off')
}
})
}
export default function* wsSagas() {const channel = yield call(initWebsocket)while (true) {
const action = yield take(channel)
yield put(action)
}
}

Keep in touch

If you have another method to integrate websockets in a Redux-based application, do not hesitate to share it as a comment.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store