WebSockets in React, the component way!

Karan Jariwala
Practo Engineering
Published in
4 min readMar 1, 2018

WebSockets are an advanced technology that makes it possible to open an interactive communication session between the user’s browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply. — MDN

So, WebSockets are the magic sauce behind your stock exchanges/crypto exchanges that make the tickers tick! Web-sockets make it possible to see the price of an entity change in real time without having to refresh the whole page every time the price changes in our Single Page App’s.

When your application grows in scope and scale, you would find at least one use case for WebSockets or where WebSockets will enhance the user experience.

Coming on to React, we know already what JQuery was to animation/ease of manipulating DOM, React is to dashboards.

Component model of react makes it an ideal choice to create interactive dashboards in an easy and a scalable

But when we need Realtime Dashboards, what do we do?

When WebSockets are implemented in React the right way, only the component whose props has changed due to the WebSockets push should re-render. Perfect!

But how do we Implement WebSockets?

One way would be to go ahead and wrap your webSocket object with a Sock JS (Read: sock) and create a wrapper function out of it. Then use your socket client API to connect, handle events and so on.

Your function would look something like this

function WebSocket(subsribeUrl, ArrayOfChannels) {
const socket = SockJS(subsribeUrl); //create wrapper
const stompClient = Stomp.over(socket);//connect using your client
stompClient.connect({}, () => {
ArrayOfChannels.forEach((channel) => {
stompClient.subscribe(channel.route, channel.callback);
});
}, () => {
setTimeout(() => {
subscribeToSocket(subsribeUrl, ArrayOfChannels);
}, 0);
});
}

Problem with this?

Nothing at all. This will work!

Also, if a webSocket gets disconnected on some client and it misses a few packets that the other clients received in the meantime. Since, It is a Single Page Application (most probably) it won’t reload when the connection re-establishes. So, your screen now is showing inconsistent data!

Solution: You would have to maintain a timestamp and send it to the server to receive the missing packets while reconnecting.

Now your modified function which maintains and sends the most recent timestamp would look something like this…

function subscribeToSocket() {
let timeStamp = new Date();
return function(subsribeUrl, ArrayOfChannels) {
const socket = SockJS(subsribeUrl);
const stompClient = Stomp.over(socket);
//sending timestamp after connect to get lost packets if any
stompClient.connect({
timeStamp
}, () => {
ArrayOfChannels.forEach((channels) => {
stompClient.subscribe(channels.route, channels.callback);
});
}, () => {
setTimeout(() => {
subscribeToSocket(subsribeUrl, ArrayOfChannels);
}, 0);
});
stompClient.onMessage(function() {
//update timestamp
timeStamp = new Date();
})
}
}();

And then may be you have to stop the recursive attempt to connect say after N tries. Not keep it in a infinite loop!

Solution: You would have to maintain a maxRetries variable and check it every time before reconnecting

Now with this your modified function would look something like …

function subscribeToSocket(){
let timeStamp= new Date();
let maxRetries;

return function (subsribeUrl, ArrayOfChannels, count) {
const socket = SockJS(subsribeUrl);
const stompClient = Stomp.over(socket);

//sending timestamp after connect to get lost packets if any
stompClient.connect({timeStamp}, () => {
// reseting maxRetries
maxRetries=count;
ArrayOfChannels.forEach((channel) => {
stompClient.subscribe(channel.route, channel.callback);
});
}, () => {
//reducing maxRetries and checking if exceeded
maxRetries=maxRetries-1;
if(maxRetries>0){
setTimeout(() => { subscribeToSocket(subsribeUrl, ArrayOfChannels);
}, 0);
}
});
stompClient.onMessage(function(){
//update timestamp
timeStamp= new Date();
})
}
}();

Here we have created local state using IIFE.

IIFE (Immediately Invoked Function Expression) is a JavaScript function that runs as soon as it is defined.

Is there a Problem with the above implementation?

The answer is no!
But if you have a React application, all the other features in your application are components. So, why not use a WebSocket?

Wouldn’t it be great, if you had a component like this?

<WebSocket subsribeUrl={subsribeUrl} ArrayOfChannels={ArrayOfChannels} {…others}/>

You could easily encapsulate state using React API without having to use IIFE. Also instead of calling this function in ComponentDidMount hooks. You can just compose this <WebSocket /> with your <Other/> components.

It would just be a component which would render empty string or a span.

Simple Implementation could look something like this…

const socket = SockJS(WEB_SOCKET_ENDPOINT);
const ws = Stomp.over(socket);
class WebSocket extends React.Component {
state = {
ws,
timeStamp: Date.now(),
maxReconnect:1
};
componentDidMount() {
this.setupWebSocket();
}
const setupWebSocket = () => {
const webSoc = this.state.ws;
webSoc.connect({}, this.connect);
webSoc.message = (body) => this.setState({ timeStamp: Date.now()});
webSoc.error = (err) => {
if (this.state.maxReconnect > 0) {
this.setState({ maxReconnect: this.state.maxReconnect - 1 }, this.connect);
}
};
}

const connect = () => {
const channels = webSocketUrls[this.props.name];
this.setState({ maxReconnect: this.props.maxReconnect });
channels.forEach((channel) => {
const webSoc=this.state.ws;
webSoc.subscribe(channel.route, channel.callback);
webSoc.send(registration.route, { timeStamp: this.state.timeStamp.toString() }, 'timeStamp');
});
}
render() {
return <span />;
}
}WebSocket.defaultProps = {
name: 'something',
maxReconnect: 5,
};

Conclusion: Even though there is nothing wrong with the IIFE implementation using Vanilla JS, I strongly believe that spending 10 minutes extra to create a Component Wrapper for your WebSocket implementation is the right thing to do in React applications!

Since all the other features in your application are most likely React Components, this way you could even further customise the above Component to take onMessage, onConnect, onError and other props to create a customisable component enterprise wide/Application wide if required.

Your teammates then would only have to interact with the wrapper component with props. (API they are already familiar with)

You could also then take the advantage of component life cycles to further optimise you WebSocket component.

It’s a Win Win!

Follow us on twitter for regular updates. If you liked this article, please hit the applause icon to recommend it. Looking for 50+ applauses. This will help other Medium users find it.

--

--

Karan Jariwala
Practo Engineering

JavaScript enthusiast, UI Developer @ripplingapp, Ex -@practo, working on React JS. Follow me on twitter @karanjariwala47