Redux offers a way to subscribe to updates in state by using connect and Provider . This lets us create dumb components that only focus on presenting the data.

Redux is about having a single source of truth and offering methods to interact with that source of truth. You dispatch events to the source of truth, the store , and that results in a new state being used to render all components hooked into connect or their children. It lets us develop reactive, event-driven applications.

This model is fantastic for closed-system state. That is, for things that happen in a dispatch -> newState -> reRender , having your single truth in a Redux store works perfectly. However, what happens when we want dispatch -> ...some time later... -> dispatch -> newState -> reRender ?

We are left with a handful of libraries that solve this via thunks, sagas, epics, etc. Each of these libraries enforces a specific style of solving this problem, a specific schema that actions/thunks/sagas/epics/et al follow. All of these are to ensure that the outside source ( usually an http request ) and our internal state talk the same language. All of them are trying to solve the same problem but in slightly different ways.

It seems that we are looking for a common way to inject information, over time, into our components, while maintaining a single source of truth that our entire application can use. It seems we are looking for a single abstraction in connecting some events in the future to updating our components. It seems we are wanting a basic publish/subscribe system.

Enter WebSockets

Most of the time when we think of making a request from the client to the server, we think in terms of REST and HTTP, of handshake/call-response interactions. For the majority of the web’s life, this type of communication has been more than enough. The web was designed to be a document web not an application web and call-response is a perfect match for document fetching.

We now find a web that is full of pieces that communicate with each other over time. These pieces might be documents or they might be scripts, API endpoints, Lamda functions, or whatever is next in terms of architecture. As the web grows, the information throughout this system updates, over time the state of the web changes.

Similarly, the state of our application grows over time and each piece needs to update to that growth in different ways. We aren’t looking for call-response communication but instead an ever-growing center of knowledge that we can interact with.

WebSockets offers a way for clients and servers to send each other information over time along the same connection. So instead of your browser asking a server for users, waiting for a response, and then doing something with that information, our server can at some point in the future tell our browser “Here are users”.

They allow us to develop reactive, event-driven applications, applications that respond and emit events throughout time, as our system grows and the state changes.

Which sounds a lot like Redux and all of the middleware that is created to interact with asynchronous actions.

The Plan

In order to keep this as simple as possible, we are going to assume that the server part of this is created and working correctly. How that is set up and how you decide to send messages between the client and the server is for another tutorial.

We are also going to assume that the messages sent to and from the server are JSON.stringify({ type, ...payload }) . This way we can lean on what Redux has taught us and keep a common mental model.

There are two components that we are going to need to build: a Socket component, that will be our WebSocket connection and the interface between that and our components, and a Subscribe component that will subscribe to updates throughout the WebSocket connection.

I’ll be using inferno for the examples but you can easily switch it to React and import React, { Component } from 'react' instead, along with changing cloneVNode to the equivalent React cloning. We will also be using RxJS for Observables and Ramda for helper functions.

Let’s start with the code in the hero image above, talking about the API we want from the components:

We want a Socket component that will basically be Provider from Redux. We also want a Subscribe component that subscribes to a list of types and updates its state based on the event.

Let’s look at Subscribe first to see the API that we want Socket to offer via context :

Subscribe is a stateful Component that either renders nothing ( if this.state.hasData === THE_BEFORE_TIME ) or it clones this.props.children , merging their props with the passed object. This way we can give children props, like dummy data, that it can use to render, and then update it whenever we have an update via the subscribe function.

Each time we create this, we set the instance’s state.hasData value to THE_BEFORE_TIME in order to not render until the subscribe function tells us to.

Before the Component will mount, we set up our subscription via this.context . We also grab reference to the updating function via handler . Then we get the event types that the child component wants to respond to, first by checking if this.props.types is an array. If it is, we set types to its value. If it not, we check to see if this.props.types is a truthy value. If it is, we set types to be [this.props.types] and if not, [] .

We also unsubscribe from updates whenever this component is unmounted.

Now that we have the interface for subscribing to updates, let’s create the Socket component:

This component is dense in code, so let’s walk through it, group by group:

const parseMessage = R.compose(

JSON.parse,

R.prop('data')

);

We create a function to parse the incoming WebSocket event. We are expecting an object with a property of data . We pass that value to the function JSON.parse and return that value.

const subscribeToTypes = obs => (types, fn) =>

obs.ofType(...types).subscribe(fn);

We create a higher order function that takes in an object that has an ofTypes method and returns a function that expects an array of types and a subscribe fn .

const publishToSocket = socket =>

action =>

socket.send(JSON.stringify(action));

A higher order function that wraps a socket with future actions .

Before this component mounts, it creates a reference to either the passed in socket or creates a new WebSocket. Then it creates an observable using an internal method. Once this.Observable has been set, we create the ofType function that the rest of our system is needing.

createObservable = socket => Rx

.Observable

.fromEvent(socket, 'message')

.map(parseMessage)

We create an Observable from the message event with the passed socket .

getChildContext = () => ({

subscribe: subscribeToTypes(this.Observable),

publish: publishToSocket(this.__socket),

})

Here we use the above higher order functions, passing in the observable and socket and setting publish and subscribe to functions waiting for the rest of the information. We use getChildContext in order to add access to the socket, via our Subscribe component, in the passed context .

Since we might have more than one child of Socket , we check to see if this.props.children is an array or not. If it is, we wrap them since inferno does not like arrays as children. If this.props.children is not an array, it returns it untouched.