Optimizing Real-Time Performance: WebSockets and React.js Integration Part I

Manuel Sanchez
9 min readFeb 9, 2024

--

react

In today’s world of lots of data, getting it fast and making sure everything works great is super important. But sometimes just asking for data over and over using the old way (HTTP requests) isn’t fast enough for today’s apps. That’s where WebSockets come in. They’re kind of like magic because they can send updates almost instantly, making sure you get the latest info right away.

And when you use them with React, it’s like a whole new way of getting info in real-time. React is really fast, but if you change too much stuff without being careful, your app might slow down or stop working. So, it’s important to be careful with how you change things in React, especially when using WebSockets.

For now, let’s start by discussing the unoptimized way of doing this. Later on, we’ll dive deeper into the discussion about the optimized way of using React with WebSockets. Stay tuned, because we’ll also explore the best practices for leveraging WebSockets for optimal performance and efficiency in your applications.

First of all what a WebSocket is? Understanding WebSockets

WebSockets provide a full-duplex communication channel over a single, long-lived connection. This enables real-time data exchange between a client (browser) and a server. Unlike traditional HTTP requests, WebSockets allow both the client and server to initiate communication, making them ideal for applications that require instant updates.

Perks of Real-Time Data Streaming

  1. Efficiency Boost: WebSockets make data updates smoother. Once connected, data flows without delay, cutting out the wait time from constant requests.
  2. Instant Updates: With WebSockets, you get updates right away. Whether you’re tracking market trends or cryptocurrency prices, you’ll always be in the loop for quick decisions.
  3. Two-Way Talk: WebSockets let servers and clients talk back and forth instantly. It’s like a fast chat where everyone gets heard, making teamwork easier.
  4. Scalability: As data needs grow, WebSockets can handle the load. They keep connections open, so even with lots of users, things run smoothly.

Using WebSockets with React

Now, let’s talk about how you can bring the power of WebSockets into your React applications with a handy tool called “react-use-websocket”. This library simplifies the process of integrating WebSockets into your React components, even for beginners.

With “react-use-websocket”, you can easily establish WebSocket connections, send and receive messages, and handle connection errors — all within your React components. Here’s how you can get started:

  1. Installation: Begin by installing the library using npm or yarn:
npm install react-use-websocket

or

yarn add react-use-websocket

2. Usage: Once installed, you can import the useWebSocket hook into your React components and start using it. Here's a basic example:

import React from 'react';
import { useWebSocket } from 'react-use-websocket';

const MyComponent = () => {
const { sendMessage, lastMessage } = useWebSocket('wss://example.com/ws');

const handleClick = () => {
sendMessage('Hello, WebSocket!');
};

return (
<div>
<button onClick={handleClick}>Send Message</button>
<p>Last Message: {lastMessage ? lastMessage.data : 'None'}</p>
</div>
);
};

export default MyComponent;

In this example, we’re using the useWebSocket hook to establish a WebSocket connection to 'wss://example.com/ws'. We can then send messages using the sendMessage function and display the last received message using the lastMessage object.

3. Handling Events: “react-use-websocket” provides hooks for handling various WebSocket events such as onOpen, onMessage, onError, and onClose. You can use these hooks to perform actions based on different WebSocket events, such as updating the UI or logging errors.

const { sendMessage, lastMessage, readyState } = useWebSocket('wss://example.com/ws', {
onOpen: () => console.log('WebSocket connection opened!'),
onClose: () => console.log('WebSocket connection closed!'),
onError: (event) => console.error('WebSocket error:', event),
onMessage: (event) => console.log('Received message:', event.data),
});

With “react-use-websocket”, integrating WebSockets into your React applications becomes straightforward and intuitive. Whether you’re building real-time chat applications, live data dashboards, or collaborative tools, “react-use-websocket” empowers you to leverage the power of WebSockets within the familiar environment of React.

Let’s make a more complete example.

For it we will use a third party websocket like cryptocompare, for that you would need to create an account to generate a free api-key, you can follow the instructions here.

After getting our api key we will add the following packages:

npm i react-use-websocket bootstrap react-bootstrap react-router-dom  --save 

or

yarn add react-use-websocket bootstrap react-bootstrap react-router-dom

Bootstrap packages are for styling

After the installation is complete let’s create the Home.tsx component in we will only contain an input text and we are going to submit our api key that will redirect us to http://localhost:3000/dashboard and add the api_key as a query parameter.

import { useState } from "react";
import Modal from "react-bootstrap/Modal";
import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import { useNavigate } from "react-router-dom";

const Home = () => {
const [show, setShow] = useState(true);
const [apiKey, setApiKey] = useState("");
const navigate = useNavigate();

const handleClose = () => setShow(false);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setApiKey(e.target.value);
};

const handleSubmit = (e: React.SyntheticEvent) => {
e.preventDefault();
navigate(`/dashboard?api_key=${apiKey}`);
};
return (
<>
<Modal
show={show}
onHide={handleClose}
data-bs-theme="dark"
style={{ color: "white" }}
>
<Modal.Header closeButton>
<Modal.Title>react use websocket</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={handleSubmit}>
<Form.Group controlId="formBasicEmail">
<Form.Label>Api key:</Form.Label>
<Form.Control
type="text"
placeholder="Please provide api key"
value={apiKey}
onChange={handleChange}
/>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleSubmit}>
Submit
</Button>
</Modal.Footer>
</Modal>
</>
);
};

export default Home;

and for the Dashboard.tsx component we will need a create an interface for the object response of the websocket:

export interface CryptoMessage {
TYPE: string;
M: string;
FSYM: string;
TSYM: string;
F: string;
ID?: string;
TS?: string;
Q?: number;
P?: number;
TOTAL?: number;
RTS?: string;
CCSEQ?: number;
TSNS?: number;
RTSNS?: number;
}

and then call the useWebSocket hook

const { readyState, sendJsonMessage, lastJsonMessage } =
useWebSocket<CryptoMessage>(socketUrl, { share: true });

Now I will explain what is readyState, sendJsonMessage, and lastJsonMessage.

The ReadyState:

The readyState property provided by the useWebSocket hook in the "react-use-websocket" library represents the current state of the WebSocket connection. It indicates whether the WebSocket connection is in a pending, open, closing, or closed state. The readyState property is an integer value that can have one of the following four values:

  1. CONNECTING (0): This state indicates that the WebSocket connection is in the process of being established. When you initially call the useWebSocket hook and start the WebSocket connection, it enters the CONNECTING state until the connection is successfully established.
  2. OPEN (1): This state indicates that the WebSocket connection is open and ready to send and receive messages. Once the connection is successfully established, it transitions to the OPEN state, allowing bidirectional communication between the client and the server.
  3. CLOSING (2): This state indicates that the WebSocket connection is in the process of being closed. When you initiate the closure of the WebSocket connection using the closeWebSocket function provided by the useWebSocket hook, it enters the CLOSING state before transitioning to the CLOSED state.
  4. CLOSED (3): This state indicates that the WebSocket connection is closed. This can happen either when the connection is closed by the server or when the client explicitly closes the connection using the closeWebSocket function. Once the connection is closed, it remains in the CLOSED state until you initiate a new WebSocket connection.

The sendJsonMessage function:

The sendJsonMessage function provided by the useWebSocket hook in the "react-use-websocket" library is a convenient utility function for sending JSON-formatted messages over a WebSocket connection. This function abstracts away the manual serialization of JavaScript objects into JSON strings, simplifying the process of sending structured data to the WebSocket server.

The lastJsonMessage:

The lastJsonMessage property provided by the useWebSocket hook in the "react-use-websocket" library represents the most recent JSON-formatted message received over the WebSocket connection. This property allows you to access and process the data contained in the last received message within your React components.

The following code displays the current WebSocket connection status on the UI:

import useWebSocket, { ReadyState } from "react-use-websocket";
import { useSearchParams } from "react-router-dom";

export interface CryptoMessage {
TYPE: string;
M: string;
FSYM: string;
TSYM: string;
F: string;
ID?: string;
TS?: string;
Q?: number;
P?: number;
TOTAL?: number;
RTS?: string;
CCSEQ?: number;
TSNS?: number;
RTSNS?: number;
}

const Dashboard = () => {
const [searchParams] = useSearchParams();
const api_key = searchParams.get("api_key");
const socketUrl = `wss://streamer.cryptocompare.com/v2?api_key=${api_key}`;
const { readyState, sendJsonMessage, lastJsonMessage } =
useWebSocket<CryptoMessage>(socketUrl, { share: true });

const connectionStatus = {
[ReadyState.CONNECTING]: "Connecting",
[ReadyState.OPEN]: "Open",
[ReadyState.CLOSING]: "Closing",
[ReadyState.CLOSED]: "Closed",
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
}[readyState];

return <>{connectionStatus} </>;
};

export default Dashboard;

Once we get the “Open” status on readyState we can send a message to our WebSocket service.

useEffect(() => {
if (readyState === ReadyState.OPEN) {
sendJsonMessage({
action: "SubAdd",
subs: [
"0~Coinbase~BTC~USD",
"0~Coinbase~BTC~EUR",
"0~Coinbase~ETH~USD",
"0~Coinbase~ETH~EUR",
],
});
}
}, [readyState, sendJsonMessage]);

Let’s explain the json object inside of sendJsonMessage:

Action:

  • The object contains a property named action with the value "SubAdd".
  • This indicates that the action to be performed on the WebSocket server is to subscribe to new data streams.

Subscriptions:

  • The object contains a property named subs which holds an array of subscription strings.
  • Each subscription string represents a specific data stream that the client wants to subscribe to.
  • The format of each subscription string is "0~{Exchange}~{BaseCurrency}~{QuoteCurrency}", where:
  • "0": Indicates a subscription to streaming data (as opposed to historical data).
  • {Exchange}: Represents the name of the exchange providing the data (e.g., "Coinbase").
  • {BaseCurrency}: Represents the base currency of the trading pair (e.g., "BTC" for Bitcoin, "ETH" for Ethereum).
  • {QuoteCurrency}: Represents the quote currency of the trading pair (e.g., "USD" for US dollars, "EUR" for Euros).

In summary, this code is instructing the WebSocket server to add new subscriptions for streaming data. The subscriptions specified in the subs array are for various trading pairs on the Coinbase exchange, including BTC/USD, BTC/EUR, ETH/USD, and ETH/EUR. This allows the client to receive real-time updates for these trading pairs from the WebSocket server.

Now we can receive the data as soon as it is available, all that is left is to show the streamed data on the browser.

const renderList = () => {
return list.map((element: CryptoMessage, index: number) => {
return (
<li key={index}>
{element.FSYM} - {element.P}
</li>
);
});
};

return <>{readyState === ReadyState.OPEN? renderList() : connectionStatus} </>;

For each element, it returns a JSX <li> element containing the value of element.FSYM (cryptocurrency symbol whether it is ETH or BTC) followed by a dash (-) and the value of element.P (Price). The result would look like this:

endless list

It would be much easier to visualize our data in a graph for it we’ll need to install the react google charts.

npm install --save react-google-charts

or

yarn add react-google-charts

then let’s import Chart, change renderList function for renderGraph instead:

const graphData: any[] = [["", "BTC", "ETH"]];
let BTCPrice;
let ETHPrice;
for (const element of list) {
if (element.FSYM === "BTC") {
BTCPrice = element.P;
} else if (element.FSYM === "ETH") {
ETHPrice = element.P;
}
if (typeof BTCPrice === "undefined" || typeof ETHPrice === "undefined") {
continue;
} else {
graphData.push(["", BTCPrice, ETHPrice]);
}
}
return (
<Chart
graph_id="graph"
chartType="LineChart"
width="100%"
height="400px"
data={graphData}
options={options}
/>
);
};

return (
<>{readyState === ReadyState.OPEN ? renderGraph() : connectionStatus} </>
);

The result would look like this:

graph

And finally here it is the working example.

Conclusion

This is a very naive way to use WebSockets because we’ve got two important issues to solve. The first one is that we are going to flood the browser with an endless list, and the second one is even if we slice the array to “n” number of elements we will have a massive re-render issue and make the application unresponsive or slow. There are some techniques to make the application run fast and smoothly but I don’t want to make this article too long so I decided to break it into two parts.

--

--