React: Managing Websockets with Redux and Context
By: Ross Bulat
Tags: Software Engineering, JavaScript, React, Programming, Web Development

Socket Manager: Websocket + Context Provider

The <SocketManager /> component can be thought of as the engine of our Websocket and state management solution. We will talk through this solution here and provide the full Github Gist thereafter.

Within <SocketManager /> we want to define the following things:

The React context itself, and the useContext() hook, giving developers the option to use the context in functional components, too

hook, giving developers the option to use the context in functional components, too The <SocketManager /> component itself, that is used to wrap the rest of the application, providing the Context Provider to the entire component tree

component itself, that is used to wrap the rest of the application, providing the Context Provider to the entire component tree Our websocket connection is managed within <SocketManager /> ’s component lifecycle methods. We’ll connect to the websocket when the component is initialised, and disconnect when it is unmounted

’s component lifecycle methods. We’ll connect to the websocket when the component is initialised, and disconnect when it is unmounted New socket events will update <SocketManager /> ’s state, and will therefore update the Context value, keeping the market data up to date

For such a setup, we require the socket.io-client package, along with react-redux for later use. Install them in your project directory:

// install dependencies yarn add socket.io-client redux react-redux

This setup assumes that you are also using the socket.io server client, run with NodeJS, whose job it is to feed your application the market data. The backend solution for this project is for another piece, but it is important to note that socket.io provide both the server side and client side APIs for websockets.

Defining the Context

Defining the Context itself is simple, also configuring the context hook to be exportable for functional components to leverage:

import React from "react";

import io from 'socket.io-client'; // defining the context with empty prices object export const SocketContext = React.createContext({

prices: {}

}); // defining a useWebsocket hook for functional components export const useWebsocket = () => React.useContext(SocketContext);

Straight away we are allowing other components access to our context, and therefore making the live market price feed reachable.

Within class components, we refer to the context using static contextType property on a class:

import { SocketContext } from '../SocketManager'; export const MyClassComponent extends React.Component {

static contextType = SocketContext;

...

}

Whereas within functional components we can use our newly defined useSocket() hook:

import { useWebsocket } from '../SocketManager'; const MyFunctionalComponent = () => {

const priceData = useWebsocket();

...

}

Whether your project strictly adheres to class or functional component, this setup has you covered.

<SocketManager /> Component

It is important to highlight that <SocketManager /> has its own internal state, that will dictate what our Context Provider will hold. Let’s firstly set up this boilerplate:

export class SocketManager extends React.Component { state = {

prices: {}

} socket = null;

...

}

A public socket class property has also been initialised to null . This property will be updated with the socket.io connection in the class constructor.

Class properties are accessible from any class function, including lifecycle methods, render and other custom methods. This is the perfect for a socket connection, whereby lifecycle methods and render require it.

If you are adopting this solution in a Typescript environment, you could also make this property private, protecting it from being mutated externally:

// Typescript friendly socket class property socket: SocketIOClient.Socket | null = null;

<SocketManager /> ’s render() will simply return the component’s children wrapped with the Context Provider:

render () {

return (

<SocketContext.Provider value={{

prices: this.state.prices

}}>

{this.props.children}

</SocketContext.Provider>

);

}

Notice that, as we mentioned above, <SocketManager /> ’s state dictates the value of our Context Provider, keeping websocket data fresh as the socket feeds in new price updates.

As you may have guessed, we can now wrap the entire app with <SocketManager /> , or individual clusters of components we wish to give context to:

// `App` root component import { SocketManager } from './SocketManager'; ... return(

<SocketManager>

<App />

</SocketManager />

);

If you know exactly which part of your app needs to leverage <SocketManager /> , it will make more sense to wrap the isolated components rather than the entire application.

Initiating the Websocket within constructor()

Now for configuring real-time updates, we can utilise the classes constructor() method for websocket initialisation.

Once we have a connected socket, we can listen for the receive prices event, where the state update can happen:

constructor (props) {

super(props); this.socket = io.connect(

process.env.NODE_ENV === 'development'

? `https://localhost:3002/`

: `https://api.mydomain.com/`

, {

transports: ['websocket'],

rejectUnauthorized: false,

secure: true

});



this.socket.on('receive prices', (payload) => {

this.setState({

prices: payload.markets

});

});

}

Examining this snippet in more detail, we have firstly initialised the socket class property with io.connect() . Notice how we have relied on the process.env.NODE_ENV environment variable to determine which endpoint to connect to:

// connect to localhost in a development environment



process.env.NODE_ENV === 'development'

? `https://localhost:3002/`

: `https://api.mydomain.com/`

We’ll discuss how to set up an encrypted websocket connection, both in development and production — relying on an Nginx Proxy — in another piece. Nonetheless, this little snippet is handy for development purposes.

Defaulting to the production URL can also be handy in development if you want to feed in live data to your development build!

Your websocket may default to the polling method if no transport config is provided. In the above snippet we only want to connect via websocket .

In my experience polling can initialise quicker, resulting in a quicker initial response from the server. However, websocket provides us a live connection in a stable manner that is more efficient in production environments.

Finally, the receive prices event is being listened to, that will update our state upon the even being triggered:

this.socket.on('receive prices', (payload) => {

this.setState({

prices: payload.markets

});

});

Handling socket disconnection

componentWillUnmount() is a great place to disconnect websockets. As there is a chance the socket may already be disconnected for whatever reason, wrap the socket.disconnect() method in a try catch statement:

componentWillUnmount () {

try {

this.socket !== null && this.socket.disconnect();

} catch (e) {

// socket not connected

}

}

Tip: Integrating React Router DOM

In the event you only wish your websocket to connect on certain pages of your app, you can always refer to react-router-dom ’s location property. Simply wrap <SocketManager /> with the withRouter() HOC provided by the package:

import { withRouter } from 'react-router-dom'; // component snipped export default withRouter(SocketManager);

Let’s now say we only wish to connect our Websocket on the landing page of our app, we can test the pathname value of location to do so, within constructor() :

constructor (props) {

super(props); if (this.props.location.pathname === '/') {

...

}

}

Or simply return false if we are not on the landing page, returning before the rest of the function is executed:

constructor (props) {

super(props); if (this.props.location.pathname !== '/') {

return false;

}

...

}

You’re not limited to location properties — the component’s props can also be tested to determine whether to initialise the websocket connection.